NDK SO 庫開發與使用中的 ABI 構架選擇

Bugtags發表於2016-06-12

Bugtags V1.2.7 引入了 NDK SO 庫,在整合的時候,遇到不同的 SO 庫打包到 APK 時,安裝在某些機器上,出現 java.lang.UnsatisfiedLinkError 載入失敗。

為此,深究了一下原理,和給出瞭解決方案。

原理

Android 系統本質是一個經過改造的 Linux 系統。最早,Android 系統只支援 ARMv5 的 CPU 構架,隨著 Android 系統的發展,又加入了 ARMv7 (2010), x86 (2011), MIPS (2012), ARMv8, MIPS64 和 x86_64 (2014)。

每一種 CPU 構架,都定義了一種 ABI(Application Binary Interface),ABI 決定了二進位制檔案如何與系統進行互動。

一般情況下,你不需要關注這些。當你的 APP 中用到了些包含 SO 庫第三方庫,或者自己使用 NDK 來實現了某些功能,你就需要認真閱讀接下來的教程。

NDK SO 支援不同的 CPU 構架

在使用 NDK 開發包含 c/c++ 程式碼的 SO 庫的時候,你可以選擇輸出支援如下 ABI CPU 構架:

armeabi armeabi­v7a arm64­v8a x86 x86_64 mips mips64 Bugtags 的 NDK 庫支援如上所有的 CPU 構架:

bugtags-ndk

但不是所有人的開發者提供的 NDK 庫都支援所有的 CPU 構架:

other-ndk

上面的這個開發者提供的庫,就只支援 armeabi。

其實一般情況下,是沒有問題的,x86 的裝置,也會相容 armeabi 的 SO 庫。

合併打包到 APK 中

如果不做任何設定,Android 的構建系統會把這些來自不同開發者的 SO 庫都合併在一起,打進 APK 壓縮包中。

├── AndroidManifest.xml ├── classes.dex ├── lib │   ├── arm64-v8a │   │   └── libBugtags.so │   ├── armeabi │   │   ├── libhyphenate.so │   │   └── libBugtags.so │   ├── armeabi-v7a │   │   └── libBugtags.so │   ├── mips │   │   └── libBugtags.so │   ├── mips64 │   │   └── libBugtags.so │   ├── x86 │   │   └── libBugtags.so │   └── x86_64 │   └── libBugtags.so ├── res

系統安裝 APK

根據官方 ndk-abi 文件, Android 系統在安裝一個 APK 的時候,會考慮當前的裝置的 CPU 構架和配置(稱為所謂的 primary-abi 和 secondary-abi),去該 APK 檔案的對應資料夾去尋找 SO 庫。

假設當前裝置是 x86 機器,會優先去 lib/x86 資料夾下尋找 SO 庫:

lib/<primary-abi>/lib<name>.so

如果找不到,同時定義了 secondary-abi,則去如下資料夾尋找:

lib/<secondary-abi>/lib<name>.so 如果找到了,就將檔案拷貝到 APK 的安裝目錄的如下資料夾中:

/lib/lib<name>.so 找不到對應的 SO,安裝正常,但是當這個 SO 在執行時被使用時,會崩潰。

問題來了

可能你已經發現問題了,當一個 APK 是這種情況:

├── AndroidManifest.xml ├── classes.dex ├── lib │   ├── arm64-v8a │   │   └── libBugtags.so │   ├── armeabi │   │   ├── libhyphenate.so │   │   └── libBugtags.so │   ├── armeabi-v7a │   │   └── libBugtags.so │   ├── mips │   │   └── libBugtags.so │   ├── mips64 │   │   └── libBugtags.so │   ├── x86 │   │   └── libBugtags.so │   └── x86_64 │   └── libBugtags.so ├── res 同時 APK 被安裝到一個 x86 的裝置上的時候,以上的尋找過程,將會失敗,執行時,將出現如下報錯:

D/xxx (10674): java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx-2/base.apk"],nativeLibraryDirectories=[/data/app/xxx-2/lib/x86, /vendor/lib, /system/lib]]] couldn't find "libirdna_sdk.so" D/xxx (10674): at java.lang.Runtime.loadLibrary(Runtime.java:366)

此處,筆者有點費解,既然在 x86 資料夾中找不到,應該去 armeabi 資料夾中自動尋找啊,此處留一個 TODO,需要接下來去確認是否是某些機器的原因。

解決方案

準則

NDK SO 開發者應該遵循一個準則:支援所有的平臺,否則將會搞砸你的使用者。

NDK SO 使用者應該遵循一個準則:要麼支援所有平臺,要麼都不支援。

然而,事與願違,因為種種原因(遺留 SO、晶片市場佔有率、APK 包大小等),並不是所有人都遵循這樣的原則。

折中方案

Android Studio

  • Android Gradle 外掛中,可以使用如下方式對 abi 進行過濾:

``` android { ...

defaultConfig {    
    ...
    ndk {
        // 設定支援的 SO 庫構架,注意這裡要根據你的實際情況來設定
        abiFilters 'armeabi'// 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
    }
}

} ``` 關鍵行:

abiFilters 'armeabi'// 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'

根據你的 APP 中使用的 SO 庫所支援的構架具體情況,你可以進行具體設定。最終輸出的 apk 中,將會包含你所選擇的 abi。

像前面舉出的例子,就應該只允許 armeabi。

  • 如果在新增 “abiFilter” 之後 Android Studio 出現以下提示:

NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin

則在專案根目錄的 gradle.properties 檔案中新增:

android.useDeprecatedNdk=true

Eclipse

Eclipse 中,你需要手動控制你的工程中的這個資料夾裡面的內容:

eclipse-libs

以達到上述的原則,使得在不同的構架的裝置上運轉正常。

參考文獻

What you should know about .so files

關於Android的.so檔案你所需要知道的)

ABI Management

相關文章