在安卓項(xiàng)目里部署so文件你需要知道的知識
1. 什么是CPU架構(gòu)及ABI
Android系統(tǒng)目前支持以下七種不同的CPU架構(gòu):ARMv***RMv7 (從2010年起),x86 (從2011年起),MIPS (從2012年起),ARMv8,MIPS64和x86_64 (從2014年起),每一種都關(guān)聯(lián)著一個(gè)相應(yīng)的ABI。
應(yīng)用程序二進(jìn)制接口(Application Binary Interface)定義了二進(jìn)制文件(尤其是.so文件)如何運(yùn)行在相應(yīng)的系統(tǒng)平臺上,從使用的指令集、內(nèi)存對齊到可用的系統(tǒng)函數(shù)庫。在Android系統(tǒng)上,每一個(gè)CPU架構(gòu)對應(yīng)一個(gè)ABI:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。
2. 為什么需要重點(diǎn)關(guān)注.so文件
如果項(xiàng)目中使用到了NDK,它將會生成.so文件,因此顯然你已經(jīng)在關(guān)注它了。如果只是使用Java語言進(jìn)行編碼,你可能在想不需要關(guān)注.so文件了吧,因?yàn)镴ava是跨平臺的。但事實(shí)上,即使你在項(xiàng)目中只是使用Java語言,很多情況下,你可能并沒有意識到項(xiàng)目中依賴的函數(shù)庫或者引擎庫里面已經(jīng)嵌入了.so文件,并依賴于不同的ABI。例如,項(xiàng)目中使用RenderScript支持庫,OpenCV,Unity,android-gif-drawable,SQLCipher等,你都已經(jīng)在生成的APK文件中包含.so文件了,而你需要關(guān)注.so文件。
Android應(yīng)用支持的ABI取決于APK中位于lib/ABI目錄中的.so文件,其中ABI可能是上面說過的七種ABI中的一種。
Native Libs Monitor這個(gè)應(yīng)用可以幫助我們理解手機(jī)上安裝的APK用到了哪些.so文件,以及.so文件來源于哪些函數(shù)庫或者框架。當(dāng)然,我們也可以自己對APP反編譯來獲取這些信息,不過相對麻煩一些。
很多設(shè)備都支持多于一種的ABI,例如ARM64和x86設(shè)備也可以同時(shí)運(yùn)行armeabi-v7a和armeabi的二進(jìn)制包。但***是針對特定平臺提供相應(yīng)平臺的二進(jìn)制包,這種情況下運(yùn)行時(shí)就少了一個(gè)模擬層(例如x86設(shè)備上模擬arm的虛擬層),從而得到更好的性能(歸功于最近的架構(gòu)更新,例如硬件fpu,更多的寄存器,更好的向量化等)。我們可以通過Build.SUPPORTED_ABIS得到根據(jù)偏好排序的設(shè)備支持的ABI列表。但你不應(yīng)該從你的應(yīng)用程序中讀取它,因?yàn)锳ndroid包管理器安裝APK時(shí),會自動選擇APK包中為對應(yīng)系統(tǒng)ABI預(yù)編譯好的.so文件,如果在對應(yīng)的lib/ABI目錄中存在.so文件的話。
3. .so文件應(yīng)該放在什么地方
我們往往很容易對.so文件應(yīng)該放在或者生成到哪里感到困惑,下面是一個(gè)總結(jié):
- Android Studio工程放在main/jniLibs/ABI目錄中(當(dāng)然也可以通過在build.gradle文件中的設(shè)置jniLibs.srcDir屬性自己指定)
 - Eclipse工程放在libs/ABI目錄中(這也是ndk-build命令默認(rèn)生成.so文件的目錄)
 - AAR壓縮包中位于jni/ABI目錄中(.so文件會自動包含到引用AAR壓縮包的APK中)
 - 最終APK文件中的lib/ABI目錄中
 - 通過PackageManager安裝后,在小于Android 5.0的系統(tǒng)中,.so文件位于app的nativeLibraryPath目錄中;在大于等于Android 5.0的系統(tǒng)中,.so文件位于app的nativeLibraryRootDir/CPU_ARCH目錄中。
 
4. 安裝Apk時(shí)PackageManagerService選擇解壓so文件的策略
在Android系統(tǒng)中,當(dāng)我們安裝Apk文件的時(shí)候,lib目錄下的so文件會被解壓App的原生庫目錄,一般來說是放到/data/data/package-name/lib目錄下,而根據(jù)系統(tǒng)和CPU架構(gòu)的不同,其拷貝策略也是不一樣的,不正確地配置so文件,比如某些App使用第三方的so時(shí),只配置了其中某一種CPU架構(gòu)的so,可能會造成App在某些機(jī)型上的適配問題。
Android版本
so拷貝策略
策略問題
5. 配置so的建議
針對Android 系統(tǒng)的這些拷貝策略的問題,我們給出了一些配置so的建議:
5.1 針對armeabi和armeabi-v7a兩種ABI
- 方法1:由于armeabi-v7a指令集兼容armeabi指令集,所以如果損失一些應(yīng)用的性能是可以接受的,同時(shí)不希望保留庫的兩份拷貝,可以移除armeabi-v7a目錄和其下的庫文件,只保留armeabi目錄;比如Apk使用第三方的so只有armeabi這一種ABI時(shí),可以考慮去掉Apk中l(wèi)ib目錄下armeabi-v7a目錄。
 - 方法2:在armeabi和armeabi-v7a目錄下各放入一份so。
 
5.2 針對x86
目前市面上的x86機(jī)型,為了兼容arm指令,基本都內(nèi)置libhoudini模塊,即二進(jìn)制轉(zhuǎn)碼支持,該模塊負(fù)責(zé)把ARM指令轉(zhuǎn)換為x86指令,所以如果是出于Apk包大小的考慮,并且可以接受一些性能損失,可以選擇刪掉x86庫目錄,x86下配置的armeabi目錄的so庫一樣可以正常加載使用。
5.3 針對64位ABI
如果App開發(fā)者打算支持64位,那么64位的so要放全,否則可以選擇不單獨(dú)編譯64位的so,全部使用32位的so,64位機(jī)型默認(rèn)支持32位so的加載。比如Apk使用第三方的so只有32位ABI的so,可以考慮去掉Apk中l(wèi)ib目錄下的64位ABI子目錄,保證Apk安裝后正常使用。
5. Android Studio配置abiFilters
- android {
 - defaultConfig {
 - ndk {
 - abiFilters 'armeabi-v7a' //, 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
 - }
 - }
 - }
 
這句話的意思就是指定NDK需要兼容的架構(gòu),把除了armeabi-v7a以外的兼容包都過濾掉,只剩下一個(gè)armeabi-v7a的文件夾。
即使我們沒有指定其他的兼容框架,也需要一個(gè)過濾。當(dāng)我們接入多個(gè)第三方庫時(shí),很可能第三方庫做了多個(gè)平臺的兼容。譬如fresco就做了各個(gè)平臺的兼容,所以它創(chuàng)建了各個(gè)兼容平臺的目錄。因?yàn)橹灰霈F(xiàn)了這個(gè)目錄,系統(tǒng)就只會在這個(gè)目錄里找.so文件而不會遍歷其他的目錄,所以就出現(xiàn)了找不到.so文件的情況。
6. java.lang.UnsatisfiedLinkError
該錯誤類型較多,以下進(jìn)行分類:
- java.lang.UnsatisfiedLinkError : dlopen failed: library //dlopen打開失敗
 - java.lang.UnsatisfiedLinkError :findLibrary returned null //找不到library
 - java.lang.UnsatisfiedLinkError : Native method not found //找不到對應(yīng)函數(shù)
 - java.lang.UnsatisfiedLinkError :Cannot load library: load_library //無法load library
 
出現(xiàn)原因:
顯然出現(xiàn)上述崩潰的根本原因是:
- (1)so無法加載,可能是so不存在等原因
 - (2)so正常加載,但是沒有找到相應(yīng)的函數(shù)
 
針對第二個(gè)原因,顯然相對來說很容易排查,而且在開發(fā)中,這樣的函數(shù)調(diào)用必然會在編譯時(shí)和debug模式下進(jìn)行測試,所以這種原因產(chǎn)生的概率很小。
那么下面主要總結(jié)幾類“so無法加載”而導(dǎo)致上述崩潰的幾種原因:
6.1 生成的so本身缺陷
一個(gè)簡單的例子:
crash堆棧:
- java.lang.UnsatisfiedLinkError: Cannot load library: find_library(linker.cpp:889): "/data/data/com.netease.nis.apptestunit/app_lib/libdemo.so" failed to load previously
 - at java.lang.Runtime.load(Runtime.java:340)
 - at java.lang.System.load(System.java:521)
 - at com.netease.nis.bugrpt.ReLinker.loadLibrary(ReLinker.java:76)
 - at com.example.crash.MainActivity.onCreate(MainActivity.java:272)
 - at android.app.Activity.performCreate(Activity.java:5220)
 - at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1086)
 - at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2193)
 - at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2279)
 - at android.app.ActivityThread.access$600(ActivityThread.java:142)
 - at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1272)
 - at android.os.Handler.dispatchMessage(Handler.java:99)
 - at android.os.Looper.loop(Looper.java:137)
 - at android.app.ActivityThread.main(ActivityThread.java:5105)
 - at java.lang.reflect.Method.invokeNative(Native Method)
 - at java.lang.reflect.Method.invoke(Method.java:511)
 - at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
 - at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
 - at dalvik.system.NativeStart.main(Native Method)
 
解決方法:
查看原項(xiàng)目Application.mk,發(fā)現(xiàn)APP_STL := gnustl_shared。原方案使用的是共享庫,這不一定都支持所有的機(jī)型,改用靜態(tài)庫gnustl_static問題解決。
對應(yīng)的在Android Studio中需要將共享庫改用靜態(tài)庫gnustl_static。這一類關(guān)于so編譯共享庫問題,需要進(jìn)行檢查。
- APP_STL 可用值
 - system 系統(tǒng)默認(rèn)
 - stlport_static - 使用STLport作為靜態(tài)庫
 - stlport_shared - 使用STLport 作為共享庫
 - gnustl_static - 使用GNU libstdc++ 作為靜態(tài)庫
 - gnustl_shared - 使用GNU libstdc++ 作為共享庫
 
上述例子只是一個(gè)簡單的例子,可能在so編譯生成時(shí),由于沒有考慮共享庫的機(jī)型匹配等原因?qū)е耈nsatisfiedLinkError崩潰,其次是64位32位系統(tǒng)架構(gòu)問題,也可能導(dǎo)致UnsatisfiedLinkError崩潰。
6.2 手機(jī)設(shè)備沒有空間
在so正確生成情況下,會根據(jù)設(shè)置的支持so庫框架生成對應(yīng)的庫。在Android系統(tǒng)中,當(dāng)我們安裝Apk文件的時(shí)候,lib目錄下的so文件會被解壓到App的原生庫目錄,一般來說是放到/data/data/package-name/lib目錄下,當(dāng)準(zhǔn)備加載native層的so時(shí),雖然在Apk中有對應(yīng)的so文件,但是由于手機(jī)設(shè)備沒有足夠的空間加載該so,導(dǎo)致加載失敗,產(chǎn)生上述崩潰。
6.3 so配置錯誤
倘若so正確生成,且手機(jī)空間充足,那么如上所述,在Android系統(tǒng)中,當(dāng)我們安裝Apk文件的時(shí)候,lib目錄下的so文件會被解壓到App的原生庫目錄,一般來說是放到/data/data/package-name/lib目錄下。但是根據(jù)系統(tǒng)和CPU架構(gòu)的不同,其拷貝策略也是不一樣的。倘若不正確地配置了so文件,比如某些App使用第三方的so時(shí),只配置了其中某一種CPU架構(gòu)的so,可能會造成App在某些機(jī)型上的適配問題,產(chǎn)生上述崩潰。
6.4 Android的PackageManager安裝問題
用戶安裝了與手機(jī)CPU架構(gòu)不符的Apk安裝包,或者App升級過程中因各種原因未正確釋放so文件。這種問題可以使用ReLinker解決。
使用ReLinker十分簡單,使用
- ReLinker.loadLibrary(context, “mylibrary”)
 
代替標(biāo)準(zhǔn)的即可。
- System.loadLibrary(“mylibrary”);
 


















 
 
 







 
 
 
 