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
armeabiv7a
arm64v8a
x86
x86_64
mips
mips64
Bugtags 的 NDK 庫支持如上所有的 CPU 構架:
但不是所有人的開發者提供的 NDK 庫都支持所有的 CPU 構架:
上面的這個開發者提供的庫,就只支持 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 中,你需要手動控制你的工程中的這個文件夾里面的內容:
以達到上述的原則,使得在不同的構架的設備上運轉正常。
參考文獻
What you should know about .so files