本篇文章已授權(quán)微信公眾號 dasu_Android(大蘇)獨家發(fā)布
這次想來講講系統(tǒng)應(yīng)用集成過程中遇到的一些坑,尤其是 so 文件相關(guān)的坑。
背景
埋這些坑的最初來源是由于測試人員在集成新終端設(shè)備時提了個 bug: app 在這個設(shè)備上無法啟動。
隨后拋來了一份日志,過濾了下,最重要的其實就一條,crash 日志:
java.lang.UnsatisfiedLinkError: No implementation found for long com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeAllocate(int) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate__I)
app 使用了 fresco 圖片庫,最初猜想是不是因為 so 文件沒有 push,因為我們的 app 是系統(tǒng)應(yīng)用,集成的時候是直接將 apk push 到 system/app 下的,因為沒有 install 過程,所以 so 文件得自己 push 到 system/lib 下。
但把機子拿過來一看,so 文件有在啊,嘗試將其刪掉,再運行,又報出了如下異常:
java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/system/app/xxxx.apk"],nativeLibraryDirectories=[/system/lib64/xxxx, /vendor/lib64, /system/lib64]]] couldn't find "libimagepipeline.so"
看了下日志,它是說,在 system/lib64 目錄下沒有找到 so 文件,奇怪,以前都是只集成到 system/lib 下就可以了啊,怎么這次多出了個 system/lib64,難道這個機子支持的 CPUABI 不一樣?試著運行了下 getprop | get cpu:

果然,這個機子支持的 CPUABI 多了個 arm64-v8a。
那這個機子既支持 arm64-v8a,又支持 armeabi-v7a,我怎么知道,我的 app 該將 so 文件集成在哪里,什么場景該放 system/lib 下,什么時候該集成到 system/lib64 中?還是說,兩個地方都放?
應(yīng)該不至于兩個目錄都得集成,因為三方應(yīng)用安裝時,從 apk 包中也只會解壓一份 so 文件而已,并不會將 lib 下所有 abi 架構(gòu)的 so 文件都解壓。
后來,試著查找相關(guān)資料,發(fā)現(xiàn)可以在 data/system/packages.xml 文件中找到自己 app 的相關(guān)配置信息,這里有明確指出該去哪里加載 so 文件,以及 app 所運行的 CPU 架構(gòu),所以我們可以運行如下命令:
cat /data/system/packages.xml | grep {你自己app的包名}

后來有些疑惑,這里的 primaryCpuAbi 屬性值,系統(tǒng)是如何確定的,因為遇到過,明明這次的值是 armeabi-v7a,但當重啟之后,有時候居然變成 arm64-v8a 了,所以就又去查找了相關(guān)資料,發(fā)現(xiàn),這個值確定的流程蠻復(fù)雜的,影響因素也很多。
那么,就沒有辦法根據(jù)某些條件確定某個場景來確定 so 文件是該放 system/lib,還是 system/lib64 了,只能兩個都集成了。于是乎,嘗試著直接將 system/lib 下的 so 文件拷貝了一份到 system/lib64,結(jié)果發(fā)現(xiàn)運行報了如下異常:
java.lang.UnsatisfiedLinkError: dlopen failed: "libimagepipeline.so" is 32-bit instead of 64-bit
哎,想當然了,不同 CPU 架構(gòu)的 so 文件肯定不一樣,哪里可以直接將 armeabi-v7a 的 so 文件放到 system/lib64 里。因此,重新編譯、打包了一份 arm64-v8a 架構(gòu)的 so 文件,集成到 system/lib64 下,再運行,搞定。
但你以為事情到這里就結(jié)束了嗎?年輕人,too yang.
由于以前 app 合作的機子,都只有 armabi-v7a 的,所以集成方式就一種,只需要集成到 system/lib 下就可以了,但由于新合作的機子有 arm64-v8a 的了,那么此時就需要修改以前的集成方式,分別將對應(yīng)的 so 文件集成到對應(yīng)的 system/lib 和 system/lib64 目錄下。
但運維人員表示說,他不懂這些,他怎么判斷說,什么時候該用舊的集成方式,什么時候用新的集成方式。我跟他說,你需要先執(zhí)行 getprop | grep cpu 命令,查看當前機子支持的 CPUABI,然后再來決定你如何集成。但運維又說,這好復(fù)雜,能否有方法就統(tǒng)一一種集成方式,不必分場景考慮。
emmm,你們都是老大,你們說了算。只能又去瞎搞了,這次去開源庫的 issue 里嘗試尋找了下,結(jié)果發(fā)現(xiàn),哈哈哈,原來這么多人碰到過這個問題:

要相信,你絕對不是第一個遇到問題的人。是吧,這么多人都來這里提問了,開源庫的負責(zé)人肯定給出解決方案了,所以接下去繼續(xù)在這些 issues 里過濾一下,找出那些跟你一樣的問題就可以了。如下面這篇:
java.lang.UnsatisfiedLinkError #1552

官方人員已經(jīng)說了,可以嘗試使用 Relinker 或 SoLoader 來解決。
最后,我選擇了 ReLinker,發(fā)現(xiàn)它的源碼并不多,直接將所有源碼拷貝到項目中,修改了源碼中某個流程的邏輯,用于解決我自己這種場景下的 so 文件加載問題,搞定,具體在下面的埋坑一節(jié)講述。
這整個過程中,遇到了一個又一個問題,一個又一個坑,解決這個異常,出現(xiàn)另一個異常,但整個過程梳理過來,也掌握了很多干貨知識點,下面就用自己的理解,將這些相關(guān)的知識點梳理一下:
知識點
看完本篇,你能了解到哪些知識點呢,如下:
P1:了解系統(tǒng)應(yīng)用集成方式,大概清楚 apk 的 install 過程都做了些什么。
P2:知道如何判斷系統(tǒng)應(yīng)用是否安裝成功,懂得查看 data/system/packages.xml 文件來得知應(yīng)用的基礎(chǔ)信息,如 so 庫地址,primaryCpuAbi 等。
P3:掌握 System.load() 和 System.loadlibrary() 的區(qū)別。
P4:清楚系統(tǒng)尋找 so 文件的大體流程,知道系統(tǒng)什么時候會去 system/lib 下加載 so 文件,什么時候去 system/lib64。
P5:了解 ReLinker 和 SoLoder 庫的用途和大體原理。
正文
ps: 由于接觸尚淺,還看不懂源碼,正文部分大多數(shù)是直接從各大神博客中梳理出的結(jié)論,再用以自己的理解表達出來,因為并沒有結(jié)合源碼來分析,因此給出的結(jié)論觀點不保證百分百正確,如有錯誤,歡迎指點一下。
ps: 以下知識點梳理基于的設(shè)備系統(tǒng) Android 5.1.1,api 22,不同系統(tǒng)的設(shè)備,也許過程會有些許差別。
1. install 過程
要了解 apk 的 install 過程都干了哪些事,先要清楚一個 apk 文件中都有哪些東西,其實 apk 文件就是一個壓縮包,后綴改為 zip 就可以直接打開查看內(nèi)容了,或者 Android Studio 的 Analyze APK 功能也可以查看:

classes.dex 是源代碼,到時候要加載進內(nèi)存運行在機器上的;lib 是存放 so 文件;res 是存放資源文件,包括布局文件、圖片資源等等;assert 同樣存放一些資源文件;AndroidManifest.xml 是清單配置文件;
既然 apk 其實就是個壓縮包,將程序運行所需要的東西,比如源代碼,比如資源文件等等都打包在一起。那么,install 的過程,其實也就是解壓&拷貝&解析的過程。
但不管是哪個過程,首先,這個 apk 文件得先傳送到終端設(shè)備上,所以,我們開發(fā)期間使用的 adb install 命令,或者是直接點擊 AndroidStudio 的 run 圖標指令(本質(zhì)上也是運行的 adb install),這個命令其實就干了兩件事:
- adb push
- pm install
先將 apk push 到終端設(shè)備的臨時目錄,大多數(shù)場景下是:data/local/tmp

如果你有注意執(zhí)行完 adb install 命令后,會先有一個百分比的進度,這個進度其實并不是安裝的進度,而是 adb push 的進度,你可以試著直接執(zhí)行 adb push 命令,看一下是否會有一個進度提示。
先將 apk 從電腦上 push 到終端設(shè)備,然后調(diào)用 pm install 命令來安裝 apk。
調(diào)用了 pm install 命令后就會通知系統(tǒng)去安裝這個 apk 了,也就是上述說的拷貝、解壓、解析這幾個過程。
拷貝是因為,存放在 data/local/tmp 下的 apk 文件始終是位于臨時目錄下,隨時可能被刪掉,系統(tǒng)會先將這個 apk 拷貝一份到 data/app 目錄下。所以,在 data/app 這個目錄下,你基本可以看到所有三方 app 的 apk 包,如果三方 app 都沒有另外指定安裝到 SD 卡的話。
拷貝結(jié)束后,就是對這個 apk 文件進行解壓操作,獲取里面的文件,將相關(guān)文件解壓到指定目錄,如:
- 創(chuàng)建 data/data/{包名} 目錄,存放應(yīng)用運行期間所需的數(shù)據(jù)
- 掃描 apk 包中 lib 目錄的 so 文件結(jié)構(gòu),解壓到應(yīng)用自身存放 so 庫的目錄,不同版本系統(tǒng)路徑有些不同,我設(shè)備的版本是 android 5.1.1,api 22,三方應(yīng)用的 so 文件存放目錄就在 data/app/{包名}-1/lib 下
- class.dex 源代碼轉(zhuǎn)換成 odex 格式,緩存到 data/dalvik-cache 目錄下,加快應(yīng)用的代碼運行效率
- 解析 AndroidManifext.xml 文件以及其他相關(guān)文件,將 app 的相關(guān)信息寫入 data/system/packages.xml 注冊表中
- 還有其他我不清楚的安裝工作
梳理一下,安裝 apk 過程中,就是解析 apk 中的內(nèi)容,然后將不同作用的文件拷貝到指定目錄中待用,涉及的目錄有:
- data/data/{包名}
- data/dalvik-cache
- data/app/{包名}-1/lib (后綴有可能是 -1,-2)
- data/system/packages.xml
我沒有找到存放 res,assert 這些資源文件的目錄,所以我猜想,這些資源文件其實并沒有解壓出來,仍舊是存放在 apk 中。我是這么猜想的:
應(yīng)用運行期間,類加載器所需的源代碼是從 data/dalvik-cache 緩存中加載,如果這里沒有緩存,則去 data/app/ 對應(yīng)的 apk 中解壓拿到 class.dex,轉(zhuǎn)換成 odex,再次緩存到 data/dalvik-cache,然后讓類加載器去加載。
而代碼運行期間所需的數(shù)據(jù)庫數(shù)據(jù),xml 數(shù)據(jù)等則直接從 data/data/{包名} 中讀取,如果代碼需要 res 或 assert 資源文件,則再去 data/app 下的 apk 中拿取。
這是我的猜想,這也才能解釋,為什么一旦將 data/app 下的 apk 刪掉,應(yīng)用就無法運行,而如果將 data/data/{包名} 以及 data/dalvik-cache 緩存的 odex 源代碼文件刪掉,應(yīng)用仍舊可以照常運行。
正確性與否不清楚,只是我的猜想,以后有時間翻閱源碼驗證一下。
小結(jié)一下
一個三方 apk 的安裝過程,不管是通過設(shè)備的有界面交互方式下的安裝,還是沒有交互界面直接通過 adb install 命令安裝,還是通過 Android Studio 的 run (本質(zhì)上是執(zhí)行 adb install 命令) 安裝。
這個過程,首先得先將 apk 文件傳送到終端設(shè)備上,設(shè)備上有了這個 apk 后,系統(tǒng)安裝應(yīng)用的過程其實也就是先將這個 apk 拷貝一份到 data/app 目錄下,然后對其進行解壓工作,將 apk 包中的 so 文件解壓出來,將 dex 文件解壓之后對其進行優(yōu)化處理緩存到 data/dalvik-cache 目錄,以便加快之后應(yīng)用的運行,最后解析 AndroidManifext.xml 文件,將這個應(yīng)用的基本信息寫入 data/system/packages.xml 文件中,然后創(chuàng)建 data/data/{包名} 目錄供應(yīng)用運行期間存放數(shù)據(jù)。
2. 系統(tǒng)應(yīng)用安裝
系統(tǒng)應(yīng)用的安裝方式就不同于三方應(yīng)用了,系統(tǒng)應(yīng)用無法通過 install 命令來安裝,其實也可以說,adb install 安裝的都是三方應(yīng)用,這些 apk 最后都被安裝到了 data/app 目錄下。
系統(tǒng)應(yīng)用只能是在出 rom 包時集成,也就是你設(shè)備第一次買來開機時就已跟隨著 rom 包自帶的應(yīng)用,除非你的應(yīng)用有 root 權(quán)限。這些應(yīng)用可以升級,但升級后權(quán)限會降為三方應(yīng)用,將不在擁有系統(tǒng)權(quán)限,但將升級后的刪掉,重啟,就又會恢復(fù)初始版本的系統(tǒng)應(yīng)用了。
這是因為,系統(tǒng)應(yīng)用的安裝過程基本都是在系統(tǒng)啟動時才去進行的。
常見的集成方式是直接將 apk 手動 push 到 system/app 目錄下,同時解壓出 apk 里面的 so 文件,手動將其 push 到 system/lib 下(大部分場景,有的需要 push 到 system/lib64)。
當 push 完成時,如果是首次 push,那么 data/system/packages.xml 注冊表中是沒有這個系統(tǒng)應(yīng)用的任何信息的,所以需要重啟一下,才能夠運行這個應(yīng)用。
系統(tǒng)在重啟的時候,會去掃描 system/app 目錄下的 apk 文件,如果發(fā)現(xiàn)這個 apk 沒有安裝,那么會去觸發(fā)它的安裝工作。這也是為什么重啟有時候會很耗時,尤其是升級完 rom 包后,因為此時需要安裝一些 apk。
而安裝過程基本跟三方應(yīng)用一樣,只是因為 apk 已經(jīng)在 system/app,所以不會將 apk 拷貝到 data/app。其余的,優(yōu)化 class.dex 格式為 odex 源代碼文件緩存到 data/dalvik-cache,寫配置到 data/system/packages.xml 中等等過程仍舊一樣。
但有一點,三方應(yīng)用的 so 文件是直接解壓到 data/app 目錄下,但系統(tǒng)應(yīng)用已不存在于 data/app 了,所以它并沒有解壓 so 文件這個過程,如果 apk 中有使用到 so 文件,那么需要自己手動 push 到 system/lib 或者 system/lib64 目錄下。
當然,也可以另外一種集成方式:
- apk push 到 system/app/{自己創(chuàng)建的目錄}/
- so 文件 push 到 system/app/{自己創(chuàng)建的目錄}/lib 中
這種方式的說明,請看后面的后記一章節(jié)。
3. packages.xml
這份配置文件在 data/system/ 目錄下,不要小看這份文件,因為不管系統(tǒng)應(yīng)用還是三方應(yīng)用,安裝過程中都會將其自身的基本信息寫入這份文件中。所以,借助這份文件,可以獲取到蠻多信息的。
比如,一般排查系統(tǒng)應(yīng)用為什么啟動不了,就可以借助這份文件。
碰到過這么一個問題,我們做的一些應(yīng)用是沒有界面的,就純粹在后臺干活。如果是三方,也許還可以通過手動去啟動這個應(yīng)用來查看相關(guān)日志,但偏偏還有些應(yīng)用是設(shè)備開機時就自啟的,所以最怕遇到的問題就是測試人員跟你說,這個應(yīng)用在某個終端上起不來。
因為這時,不清楚這個應(yīng)用到底是不是因為代碼問題導(dǎo)致一直崩潰,起不來;還是因為根本就沒安裝成功;所以,遇到這類問題,第一點就是要先確認這一點,而確認這一點,就可以借助 packages.xml 這份配置文件了。
如果能夠在這份 packages.xml 配置文件中找到應(yīng)用的信息,那么說明安裝成功了,接下去就往另一個方向排查問題了。
還有一種場景借助這份配置文件分析也是很有幫助的。
我們還遇到這種情況:
首先 system/app 下是系統(tǒng)應(yīng)用,data/app 下是三方應(yīng)用,但系統(tǒng)是允許 system/app 和 data/app 下存在相同包名的應(yīng)用,因為允許系統(tǒng)應(yīng)用進行升級操作,只是此時系統(tǒng)應(yīng)用將變成三方應(yīng)用權(quán)限。
某次,有反饋說,system/app 下已集成了最新版本的應(yīng)用,但為什么,每次啟動應(yīng)用時,運行的都是舊版本。這時候怎么排查,就是根據(jù) packages.xml 中這個應(yīng)用的基本信息,它包括,這個應(yīng)用的版本號,apk 的來源目錄,so 文件的加載地址,所申請的權(quán)限等等。
有了這些信息,足夠確認,此刻運行的應(yīng)用是 data/app 下的 apk,還是 system/app 下的 apk。確認了之后,再進一步去排查。
4. System.load 和 System.loadlibrary
load() 和 loadlibrary() 都是用來加載 so 文件的,區(qū)別僅在于 load() 接收的是絕對路徑,比如 ”data/data/{包名}/lib/xxx.so“ 這樣子,因為是絕對路徑,所以最后跟著的是 so 文件全名,包括后綴名。
而 loadlibrary() 只需傳入 so 文件去頭截尾的名字就可以了,如 libblur.so,只需傳入 blur 即可,內(nèi)部會自動補全 so 文件可能存在的路徑,以及補全 lib 前綴和 .so 后綴。
所以,下面要講的,其實就是 loadlibrary() 加載 so 文件的流程。
因為之前碰到過這么個問題,有些不大理解:
我們的應(yīng)用是系統(tǒng)應(yīng)用,那么 so 文件也就是集成到 system/lib 或者 system/lib64 目錄下,但不清楚,程序是根據(jù)什么決定是應(yīng)該去 system/lib 目錄下加載 so 文件,還是去 system/lib64 下加載,或者兩處都會去?
所以,下個小節(jié)就是講這個。
5. so 文件加載流程
這節(jié)是本篇的重點,打算親自過下源碼來梳理,但這樣篇幅會特別長,基于此,就另起一篇來專門寫從源碼中梳理 so 文件的加載流程吧,這里就只給出鏈接和幾點結(jié)論,感興趣的可以去看看。
- 一個應(yīng)用在安裝過程中,系統(tǒng)會經(jīng)過一系列復(fù)雜的邏輯確定兩個跟 so 文件加載相關(guān)的 app 屬性值:nativeLibraryDirectories ,primaryCpuAbi ;
- nativeLibraryDirectories 表示應(yīng)用自身存放 so 文件的目錄地址,影響著 so 文件的加載流程;
- primaryCpuAbi 表示應(yīng)用應(yīng)該運行在哪種 abi 上,如(armeabi-v7a),它影響著應(yīng)用是運行在 32 位還是 64 位的進程上,進而影響到尋找系統(tǒng)指定的 so 文件目錄的流程;
- 以上兩個屬性,在應(yīng)用安裝結(jié)束后,可在 data/system/packages.xml 中查看;
- 當調(diào)用 System 的
loadLibrary()加載 so 文件時,流程如下: - 先到 nativeLibraryDirectories 指向的目錄中尋找,是否存在且可用的 so 文件,有則直接加載這里的 so 文件;
- 上一步?jīng)]找到的話,則根據(jù)當前進程如果是 32 位的,那么依次去 vendor/lib 和 system/lib 目錄中尋找;
- 同樣,如果當前進程是 64 位的,那么依次去 vendor/lib64 和 system/lib64 目錄中尋找;
- 當前應(yīng)用是運行在 32 位還是 64 位的進程上,取決于系統(tǒng)的 ro.zygote 屬性和應(yīng)用的 primaryCpuAbi 屬性值,系統(tǒng)的 ro.zygote 可通過執(zhí)行 getprop 命令查看;
- 如果 ro.zygote 屬性為 zygote64_32,那么應(yīng)用啟動時,會先在 ro.product.cpu.abilist64 列表中尋找是否支持 primaryCpuAbi 屬性,有,則該應(yīng)用運行在 64 位的進程上;
- 如果上一步不支持,那么會在 ro.product.cpu.abilist32 列表中尋找是否支持 primaryCpuAbi 屬性,有,則該應(yīng)用運行在 32 位的進程上;
- 如果 ro.zygote 屬性為 zygote32_64,則上述兩個步驟互換;
- 如果應(yīng)用的 primaryCpuAbi 屬性為空,那么以 ro.product.cpu.abilist 列表中第一個 abi 值作為應(yīng)用的 primaryCpuAbi;
- 運行在 64 位的 abi 有:arm64-v8a,mips64,x86_64
- 運行在 32 位的 abi 有:armeabi-v7a,armeabi,mips,x86
- 通常支持 arm64-v8a 的 64 位設(shè)備,都會向下兼容支持 32 位的 abi 運行;
- 但應(yīng)用運行期間,不能混合著使用不同 abi 的 so 文件;
- 比如,當應(yīng)用運行在 64 位進程中時,無法使用 32 位 abi 的 so 文件,同樣,應(yīng)用運行在 32 位進程中時,也無法使用 64 位 abi 的 so 文件;
6. 三方庫 ReLinker 和 Soloder
ReLinker 和 Soloder 都是用于解決一些 so 文件加載失敗的場景,比如:
- 嵌套的 so 文件加載異常,如程序引用了三方庫,三方庫又引用了三方庫,各自庫中又都存在 so 文件加載,有時候可能會導(dǎo)致 so 文件加載失敗。
- so 文件缺失導(dǎo)致加載異常,如程序的 so 文件在設(shè)備的 so 目錄中不見了之類的異常。
- 等等
它們的 Github 地址:
SoLoader:https://github.com/facebook/SoLoader
ReLinker:https://github.com/KeepSafe/ReLinker
ReLinker 的原理我有去源碼梳理了一遍,大體上是這樣:
- 先調(diào)用系統(tǒng)
System.loadlibrary()加載 so 文件,如果成功,結(jié)束; - 如果失敗,則重新解壓 apk 文件,解析其中的 lib 目錄,遍歷 so 文件,找到所需的 so 文件時,將其緩存一份至 data/data/{包名}/app-lib 目錄下,調(diào)用
System.load()加載這份 so 文件; - 之后每次應(yīng)用重啟,仍舊先調(diào)用系統(tǒng)的
System.loadlibrary()去嘗試加載 so 文件,失敗,如果 app-lib 下有緩存,且可用,則加載這個緩存的 so 文件,否則重新解壓 apk,繼續(xù) 2 步驟。
- 當然,解壓 apk 遍歷 so 文件時,如果需要的 so 文件存在于不同的 CPU 架構(gòu)目錄中,并不加以區(qū)分,直接拿第一個遍歷到的 so 文件。
SoLoder 的原理我只是稍微過了下,并沒有詳細看,因為我最后選擇的是 ReLinker 方案,但也可以大體上說一說:
- 遍歷設(shè)備所有存放 so 文件的目錄,如 system/lib, vendor/lib,緩存其中所有的 so 文件名。
- 如果系統(tǒng)加載 so 文件失敗時,則從緩存的所有 so 文件名列表中尋找是否有和當前要加載的 so 文件一致的,有則直接加載這個 so 文件。
原理大體上應(yīng)該是這樣,感興趣可以自行去看一下。
那么,這兩個 so 文件加載的開源庫有什么用呢?看你是否有遇到過 so 文件加載異常了,我的應(yīng)用場景在埋坑一節(jié)里細說。
埋坑
好了,理論基礎(chǔ)都已經(jīng)有了,那么接下去就是來埋坑了。
針對開頭所遇到的 bug,其實原因歸根結(jié)底就是沒有加載到正確的 so 文件,比如程序需要加載的是 system/lib64 下的 so 文件,但運維人員只集成到 system/lib 中;甚至說,運維人員連 so 文件都忘記集成到 system/lib 下了。
另外,運維人員希望,可以有一種統(tǒng)一的集成方法,他不需要去考慮是否還要根據(jù)其他條件來判斷他是否要集成到 system/lib 還是 system/lib64 還是兩者都要。
那么,解決方案其實有兩種,一是給他一個新的無需考慮場景的集成方式;二是代碼層面做適配,動態(tài)去加載所缺失的或不能用的 so 文件。
方案一:系統(tǒng)應(yīng)用集成方式
假設(shè)需要集成的應(yīng)用包名:com.dasu.shuai,apk 文件名:dasu.apk
- 在 system/app 目錄下新建子目錄,命名能表示那個應(yīng)用即可,如:dasu
- 將 dasu.apk push 到 system/app/dasu/ 目錄下
- 在 system/app/dasu 目錄下新建子目錄:lib/arm,這個命名是固定的,這樣系統(tǒng)才可以識別
- apk 編譯打包時,可以刪掉其他 CPU 架構(gòu)的 so 文件,只保留 armeabi-v7a 即可(根據(jù)你們應(yīng)用的用戶設(shè)備場景為主)
- 解壓 apk 文件,取出里面的 lib/armeabi-v7a 下的 so 文件,push 到 system/lib 或 system/app/dasu/lib/arm 都可以
- 重啟(如果應(yīng)用首次集成需重啟,否則 packages.xml 中無該應(yīng)用的任何信息)
以上方案是針對我們應(yīng)用自己的用戶群場景的集成方式,如果想要通用,最好注意一下步驟 3 和 4,上述的這兩個步驟目的在于讓系統(tǒng)將該應(yīng)用的 primaryCpuAbi 屬性識別成 armeabi-v7a,這樣就無需編譯多個不同架構(gòu)的 so 文件,集成也只需集成到 system/lib 目錄中即可。
系統(tǒng)在掃描到 lib/arm 有這個目錄存在時,會將 app 的 primaryCpuAbi 設(shè)置成 armeabi-v7a,相對應(yīng)的,如果是 lib/arm64,那么就設(shè)置成 arm64-v8a,這是在 api22 機子上測試的結(jié)果。
方案二:代碼適配
清楚了 ReLinker 的原理后,其實只要修改其中一個小小的流程即可。當系統(tǒng)加載 so 文件異常,ReLinker 接手來繼續(xù)尋找 so 文件時,進行到解壓 apk 包遍歷所有 so 文件時,如果有多個不同 CPU 架構(gòu)的 so 文件,此時修改原本的以第一個遍歷到的 so 文件的邏輯,將其修改成尋找與此時應(yīng)用的 primaryCpuAbi 一致的架構(gòu)目錄下的 so 文件來使用。
我是兩種方案都做了,如果運維能夠按照正常步驟集成,那么 so 文件加載異常的概率應(yīng)該就不會大,即使運維哪個步驟操作失誤了,方案二也可以彌補。
后記
本來以為這樣子的解決方案足夠解決這個問題了,也達到了運維人員的需求了。但沒想到,事后居然又發(fā)現(xiàn)了新的問題:
由于我們是使用 fresco 圖片庫的,所以我們 app 的 so 文件其實都是來自 fresco 的,但沒想到,合作的廠商它們自己的 app 也是使用的 fresco,然后他們也需要集成 so 文件。
但由于都是作為系統(tǒng)應(yīng)用集成,so 文件都是統(tǒng)一集成在同一個目錄中,如 system/lib,那么我們使用的 fresco 的 so 文件肯定就跟他們的 so 文件沖突了,因為文件名都一致,最后集成的時候就只使用他們的 so 文件。
然后,我們使用的 fresco 版本還跟他們不一樣,結(jié)果就導(dǎo)致了,使用他們的 so 文件,我們的 app 運行時仍舊會報:
java.lang.UnsatisfiedLinkError: No implementation found for long com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeAllocate(int) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate__I)
那要確認不同版本的 fresco 的 so 文件究竟有哪些差異,也只能去期待 fresco 官網(wǎng)是否有給出相關(guān)的文章。一般來說,新版本應(yīng)該能兼容舊版本才對,這也就意味著,我們使用的版本其實比合作方他們新,如果集成時,使用的是我們的 so 文件,雙方應(yīng)該就都沒問題。但跟他們合作一起集成時,如何來判斷誰使用的版本新,誰的舊?都不更新的嗎?
畢竟人家是廠商,我們只是需求合作,我們?nèi)鮿?,那還是我們自己再來想解決方案吧。
原本的 ReLinker 方案只能解決 so 文件不存在,加載失敗,或者 so 文件 abi 異常的問題,但解決不了,so 文件的版本更新問題。
如果真要從代碼層面著手,也不是不行,每次加載 so 文件前,先手動去系統(tǒng)的 so 文件目錄中,將即將要加載的 so 文件進行一次 md5 計算,程序中可以保存打包時使用的 so 文件的 md5 值,兩者相互比較,來判斷 so 文件對應(yīng)的代碼版本是否一致。但這樣會導(dǎo)致正常的流程需要額外處理一些耗時工作,自行評估吧。
或者,讓運維人員在集成時,干脆不要將 so 文件集成到 system/lib 目錄中,直接集成到 system/app/{新建目錄}/lib/arm/ 目錄下,這樣我們就只使用我們自己的 so 文件,不用去擔(dān)心跟他們共用時,版本差異問題了。
參考資料
2.Android程序包管理(2)--使用adb install執(zhí)行安裝過程
大家好,我是 dasu,歡迎關(guān)注我的公眾號(dasuAndroidTv),如果你覺得本篇內(nèi)容有幫助到你,可以轉(zhuǎn)載但記得要關(guān)注,要標明原文哦,謝謝支持~
