前言
首先要知道,64位的設備是兼容32位so文件的,目前很多主流app都是只在app中放置32位so,目的是減小apk的打包體積,弊端就是在64位設備上運行時不能充分發揮64位cpu的計算能力。
但是我們項目沒有這么做,而是在app中同時放了32和64位so,導致apk的體積暴增,原因是app中有3D圖形加載比較耗cpu,因此在64位設備上運行為了實際體驗使用64位的so,而其他功能如mp3轉碼,pdf查看也需要用到so文件,直接用32位就夠了。那么問題來了,有沒有可能在app中只放置加載圖形需要的64位so,和其他功能需要的32位so呢?讓32和64位so混合使用。
嘗試過程
先說結論,如果用64位so的功能沒有UI交互(比如視頻轉碼這類),有辦法解決。如果這部分功能需要界面交互的,目前無法解決。
查了一些技術文章,是說android不允許混合使用so文件,要么全部使用32位要么全部使用64位,總之在lib目錄文件要放全,否則加載的時候找不到對應文件就會崩潰。那我先嘗試了多進程方式動態加載,就是在lib目錄只放了部分32位so,然后我把需要64位so的圖形功能Activity放到新的進程,動態加載64位so文件,還是報錯。
接著嘗試蒙騙lib文件夾,我直接把64位so放到armeabi目錄,企圖蒙騙過關,但是加載的時候就報錯了,虛擬機發現armeabi里面放的不是32位so。。。
接著嘗試,app項目中什么so都不放,先動態加載32位,再動態加載64位so。還是不行
試了一天沒辦法了,我決定去stackoverflow碰碰運氣,這真是神奇的網站啊,這么偏的問題這么蹩腳的英文提問,還是有大神來圍觀。
https://stackoverflow.com/questions/45353090/how-to-mix-32-and-64-bit-so-files-in-an-app
大致意思就是,Android在安裝apk的時候就已經決定了,加載32還是64位so只能選其一,但是可以嘗試用c++源碼編譯64位的linux可執行文件,跳過JNI調用直接使用Runtime.exec用android的運行時來命令行調用。這樣就可以在app中混合使用32和64位so了,但是這樣無法解決有UI交互的功能。
OK,我們項目的圖形加載當然是有UI交互的,這個問題目前沒有解決,不過學到了兩點新姿勢:一是Android加載so文件機制,二是如何動態加載so文件。
android加載so文件的機制
apk在安裝的過程中,系統就會對apk進行解析根據里面so文件類型,確定這個apk安裝是在32 還是 64位的虛擬機上,如果是32位虛擬機那么就不能使用64位so,如果是64位虛擬機也不能使用32位so。而64位設備可以提供32和64位兩種虛擬機,根據apk選擇開啟哪一種,因此說64位設備兼容32的so庫。
具體機制,分下面四種情況:
1.假設apk的lib目錄放置了32和64位兩種so,那么安裝時根據當前設備的cpu架構從上到下篩選(X86 > arm64 > arm32),一旦發現lib里面有和設備匹配的so文件,那么直接選定這種架構為標準。比如當前設備是64位并且發現lib有一個64位的so,那么apk會拷貝lib下所有64位的so文件到data/data/packageName/lib/目錄(查看此目錄需要ROOT)
后面調用System.loadLibrary其實就是加載這個目錄下的so文件,此時如果有某個64位so文件在我們項目的lib中沒有提供,就會直接報錯程序崩潰。因此這里如果放置部分功能32位so,部分功能放置放置64位so,即使用多進程來加載模型,也會報錯崩潰。
2.apk的lib目錄只放置32位so,參照上面原理,運行在32位設備是OK的。絕大多數64位設備也是OK的,不過x86的設備肯定會崩潰。假設現在運行在64位設備,然后在代碼中動態加載64位so文件,會報錯:so is 64-bit instead of 32-bit
3.apk的lib目錄只放64位的so,那這個apk只能運行在64位的設備了,同理如果在代碼中動態加載32位的so,會報錯:so is 32-bit instead of 64-bit
4.apk的lib不放任何so文件,全部動態加載。安裝在32位設備就只能加載32位so,安裝在64位的設備系統會默認你的apk運行在64位虛擬機,此時動態加載32位so也是不行的。
so的動態加載
先說靜態加載:開發階段把so庫放到項目lib目錄,安裝apk時系統會拷貝這些文件到data/data/packageName/lib/目錄,System.loadLibrary()這個方法沒有指定so文件的絕對路徑,因為系統會直接去這個目錄找。這種加載是常用的so文件使用方法,弊端就是可能是apk的體積變得很大。
這種情況可以考慮使用動態加載:apk不放置任何so文件在安裝運行后,根據功能需求從服務端下載對應的so文件,拷貝到指定目錄然后調用System.load(so的的絕對路徑)來加載。
動態加載的弊端就是需要服務端的額外工作量,以及so文件的后期維護,目前我在在項目中沒有實際運用。不過我把so放到Asset目錄,然后動態拷貝到指定目錄是可行的,通過Demo發現有兩個需要注意的地方:
- 動態加載的目錄并不是data/data/packageName/lib/,這個目錄只讀不可寫,而是這個目錄:
String libPath = "/data/data/" + getPackageName(); // Your application path
可以先下載so文件到SD卡,然后拷貝so到上面的libPath(不需要特殊權限),接著調用System.load(libPath + "/libmp3lame.so")實現動態加載。
- 現在有很多主流app都拋棄64和x86的so文件,只采用32位。如果你也想只使用32位的so并且是動態加載,你可以發現app運行在64位手機上加載so時直接崩潰,原因參照上面so加載機制的第四條。解決辦法就是:app的lib目錄放置一個32位的so文件(可以放一個體積很小的)
參考文檔: