標籤:bin 混合 x86_64 project drawable try build 產生 ros
早期的Android系統差點兒僅僅支援ARMv5的CPU架構,你知道如今它支援多少種嗎?7種。
Android系統眼下支援以下七種不同的CPU架構:ARMv5。ARMv7 (從2010年起),x86 (從2011年起),MIPS (從2012年起),ARMv8,MIPS64和x86_64 (從2014年起),每一種都關聯著一個相應的ABI。
應用程式二進位介面(Application Binary Interface)定義了二進位檔案(尤其是.so檔案)怎樣執行在相應的系統平台上。從使用的指令集,記憶體對齊到可用的系統函數庫。
在Android系統上。每一個CPU架構相應一個ABI:armeabi,armeabi-v7a,x86。mips,arm64-v8a,mips64,x86_64。
1.為什麼你須要重點關注.so檔案
假設項目中使用到了NDK,它將會產生.so檔案,因此顯然你已經在關注它了。假設僅僅是使用Java語言進行編碼,你可能在想不須要關注.so檔案了吧,由於Java是跨平台的。
但其實,即使你在項目中僅僅是使用Java語言,非常多情況下,你可能並沒有意識到項目中依賴的函數庫或者引擎庫裡面已經嵌入了.so檔案,並依賴於不同的ABI。
比如,項目中使用RenderScript支援庫,OpenCV。Unity,android-gif-drawable,SQLCipher等,你都已經在產生的APK檔案裡包括.so檔案了。而你須要關注.so檔案。
Android應用支援的ABI取決於APK中位於lib/ABI檔案夾中的.so檔案。當中ABI可能是上面說過的七種ABI中的一種。
Native Libs Monitor 這個應用能夠協助我們理解手機上安裝的APK用到了哪些.so檔案,以及.so檔案來源於哪些函數庫或者架構。
當然。我們也能夠自己對app反編譯來擷取這些資訊,只是相對麻煩一些。
非常多裝置都支援多於一種的ABI。比如ARM64和x86裝置也能夠同一時候執行armeabi-v7a和armeabi的二進位包。
但最好是針對特定平台提供相應平台的二進位包。這樣的情況下執行時就少了一個類比層(比如x86裝置上類比arm的虛擬層),從而得到更好的效能(歸功於近期的架構更新,比如硬體fpu。很多其它的寄存器,更好的向量化等)。
我們能夠通過Build.SUPPORTED_ABIS得到依據偏好排序的裝置支援的ABI列表。但你不應該從你的應用程式中讀取它,由於Android包管理器安裝APK時,會自己主動選擇APK包中為相應系統ABI先行編譯好的.so檔案,假設在相應的lib/ABI檔案夾中存在.so檔案的話。
2.App中可能出錯的地方
處理.so檔案時有一條簡單卻並不知名的重要法則。
你應該儘可能的提供專為每一個ABI最佳化過的.so檔案,但要麼全部支援。要麼都不支援:你不應該混合著使用。你應該為每一個ABI檔案夾提供相應的.so檔案。
當一個應用安裝在裝置上。僅僅有該裝置支援的CPU架構相應的.so檔案會被安裝。
在x86裝置上,libs/x86檔案夾中假設存在.so檔案的話。會被安裝,假設不存在,則會選擇armeabi-v7a中的.so檔案。假設也不存在,則選擇armeabi檔案夾中的.so檔案(由於x86裝置也支援armeabi-v7a和armeabi)。
3.其它地方也可能出錯
當你引入一個.so檔案時,不止影響到CPU架構。我從其它開發人員那裡能夠看到一系列常見的錯誤,當中最多的是"UnsatisfiedLinkError","dlopen: failed"以及其它類型的crash或者低下的效能:
使用android-21平台版本號碼編譯的.so檔案執行在android-15的裝置上
使用NDK時。你可能會傾向於使用最新的編譯平台,但其實這是錯誤的,由於NDK平台不是與舊版相容的,而是前向相容的。推薦使用app的minSdkVersion相應的編譯平台。
這也意味著當你引入一個先行編譯好的.so檔案時,你須要檢查它被編譯所用的平台版本號碼。
4.混合使用不同C++執行時編譯的.so檔案
.so檔案能夠依賴於不同的C++執行時,靜態編譯或者動態載入。混合使用不同版本號碼的C++執行時可能導致非常多奇怪的crash,是應該避免的。
作為一個經驗法則。當僅僅有一個.so檔案時,靜態編譯C++執行時是沒問題的,否則當存在多個.so檔案時。應該讓全部的.so檔案都動態連結同樣的C++執行時。
這意味著當引入一個新的先行編譯.so檔案,並且項目中還存在其它的.so檔案時。我們須要首先確認新引入的.so檔案使用的C++執行時是否和已經存在的.so檔案一致。
5.沒有為每一個支援的CPU架構提供相應的.so檔案
這一點在前文已經說到了,但你應該真的特別注意它,由於它可能發生在根本沒有意識到的情況下。
比如:你的app支援armeabi-v7a和x86架構。然後使用Android Studio新增了一個函數庫依賴,這個函數庫包括.so檔案並支援很多其它的CPU架構,比如新增android-gif-drawable函數庫:
compile ‘pl.droidsonroids.gif:android-gif-drawable:1.1.+’
公布我們的app後,會發現它在某些裝置上會發生Crash,比如Galaxy S6,終於能夠發現僅僅有64位檔案夾下的.so檔案被安裝進手機。
解決方式:又一次編譯我們的.so檔案使其支援缺失的ABIs,或者設定
ndk.abiFilters
顯示指定支援的ABIs。
最後一點: 假設你是一個SDK提供者,但提供的函數庫不支援全部的ABIs。那你將會搞砸你的使用者,由於他們能支援的ABIs必將僅僅能少於你提供的。
將.so檔案放在錯誤的地方
我們往往非常easy對.so檔案應該放在或者產生到哪裡感到困惑,以下是一個總結:
- Android Studio工程放在jniLibs/ABI檔案夾中(當然也能夠通過在build.gradle檔案裡的設定jniLibs.srcDir屬性自己指定)
- Eclipse工程放在libs/ABI檔案夾中(這也是ndk-build命令預設產生.so檔案的檔案夾)
- AAR壓縮包中位於jni/ABI檔案夾中(.so檔案會自己主動包括到引用AAR壓縮包的APK中)
- 終於APK檔案裡的lib/ABI檔案夾中
- 通過PackageManager安裝後,在小於Android 5.0的系統中,.so檔案位於app的nativeLibraryPath檔案夾中;在大於等於Android 5.0的系統中,.so檔案位於app的nativeLibraryRootDir/CPU_ARCH檔案夾中。
僅僅提供armeabi架構的.so檔案而忽略其它ABIs的
全部的x86/x86_64/armeabi-v7a/arm64-v8a裝置都支援armeabi架構的.so檔案。因此似乎移除其它ABIs的.so檔案是一個降低APK大小的好技巧。但其實並非:這不僅僅影響到函數庫的效能和相容性。
x86裝置能夠非常好的執行ARM類型函數庫。但並不保證100%不發生crash,特別是對舊裝置。64位裝置(arm64-v8a, x86_64, mips64)能夠執行32位的函數庫,可是以32位元模式執行,在64位平台上執行32位版本號碼的ART和Android組件。將丟失專為64位最佳化過的效能(ART。webview,media等等)。
以降低APK包大小為由是一個錯誤的借口。由於你也能夠選擇在應用市場上傳指定ABI版本號碼的APK,產生不同ABI版本號碼的APK能夠在build.gradle中例如以下配置:
android { ... splits { abi { enable true reset() include ‘x86‘, ‘x86_64‘, ‘armeabi-v7a‘, ‘arm64-v8a‘ //select ABIs to build APKs for universalApk true //generate an additional APK that contains all the ABIs } } // map for the version code project.ext.versionCodes = [‘armeabi‘: 1, ‘armeabi-v7a‘: 2, ‘arm64-v8a‘: 3, ‘mips‘: 5, ‘mips64‘: 6, ‘x86‘: 8, ‘x86_64‘: 9] android.applicationVariants.all { variant -> // assign different version code for each output variant.outputs.each { output -> output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode } }}
關於Android的.so檔案所須要知道的