這次想來講講系統應用集成過程中遇到的一些坑,尤其是 so 文件相關的坑。 背景 埋這些坑的最初來源是由於測試人員在集成新終端設備時提了個 bug: app 在這個設備上無法啟動。 隨後拋來了一份日誌,過濾了下,最重要的其實就一條,crash 日誌: app 使用了 fresco 圖片庫,最初猜想是不 ...
這次想來講講系統應用集成過程中遇到的一些坑,尤其是 so 文件相關的坑。
背景
埋這些坑的最初來源是由於測試人員在集成新終端設備時提了個 bug: app 在這個設備上無法啟動。
隨後拋來了一份日誌,過濾了下,最重要的其實就一條,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 是系統應用,集成的時候是直接將 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 中?還是說,兩個地方都放?
應該不至於兩個目錄都得集成,因為三方應用安裝時,從 apk 包中也只會解壓一份 so 文件而已,並不會將 lib 下所有 abi 架構的 so 文件都解壓。
後來,試著查找相關資料,發現可以在 data/system/packages.xml 文件中找到自己 app 的相關配置信息,這裡有明確指出該去哪裡載入 so 文件,以及 app 所運行的 CPU 架構,所以我們可以運行如下命令:
cat /data/system/packages.xml | grep {你自己app的包名}
後來有些疑惑,這裡的 primaryCpuAbi 屬性值,系統是如何確定的,因為遇到過,明明這次的值是 armeabi-v7a,但當重啟之後,有時候居然變成 arm64-v8a 了,所以就又去查找了相關資料,發現,這個值確定的流程蠻複雜的,影響因素也很多。
那麼,就沒有辦法根據某些條件確定某個場景來確定 so 文件是該放 system/lib,還是 system/lib64 了,只能兩個都集成了。於是乎,嘗試著直接將 system/lib 下的 so 文件拷貝了一份到 system/lib64,結果發現運行報瞭如下異常:
java.lang.UnsatisfiedLinkError: dlopen failed: "libimagepipeline.so" is 32-bit instead of 64-bit
哎,想當然了,不同 CPU 架構的 so 文件肯定不一樣,哪裡可以直接將 armeabi-v7a 的 so 文件放到 system/lib64 里。因此,重新編譯、打包了一份 arm64-v8a 架構的 so 文件,集成到 system/lib64 下,再運行,搞定。
但你以為事情到這裡就結束了嗎?年輕人,too yang.
由於以前 app 合作的機子,都只有 armabi-v7a 的,所以集成方式就一種,只需要集成到 system/lib 下就可以了,但由於新合作的機子有 arm64-v8a 的了,那麼此時就需要修改以前的集成方式,分別將對應的 so 文件集成到對應的 system/lib 和 system/lib64 目錄下。
但運維人員表示說,他不懂這些,他怎麼判斷說,什麼時候該用舊的集成方式,什麼時候用新的集成方式。我跟他說,你需要先執行 getprop | grep cpu
命令,查看當前機子支持的 CPUABI,然後再來決定你如何集成。但運維又說,這好複雜,能否有方法就統一一種集成方式,不必分場景考慮。
emmm,你們都是老大,你們說了算。只能又去瞎搞了,這次去開源庫的 issue 里嘗試尋找了下,結果發現,哈哈哈,原來這麼多人碰到過這個問題:
要相信,你絕對不是第一個遇到問題的人。是吧,這麼多人都來這裡提問了,開源庫的負責人肯定給出解決方案了,所以接下去繼續在這些 issues 里過濾一下,找出那些跟你一樣的問題就可以了。如下麵這篇:
java.lang.UnsatisfiedLinkError #1552
官方人員已經說了,可以嘗試使用 Relinker 或 SoLoader 來解決。
最後,我選擇了 ReLinker,發現它的源碼並不多,直接將所有源碼拷貝到項目中,修改了源碼中某個流程的邏輯,用於解決我自己這種場景下的 so 文件載入問題,搞定,具體在下麵的埋坑一節講述。
這整個過程中,遇到了一個又一個問題,一個又一個坑,解決這個異常,出現另一個異常,但整個過程梳理過來,也掌握了很多乾貨知識點,下麵就用自己的理解,將這些相關的知識點梳理一下:
知識點
看完本篇,你能瞭解到哪些知識點呢,如下:
P1:瞭解系統應用集成方式,大概清楚 apk 的 install 過程都做了些什麼。
P2:知道如何判斷系統應用是否安裝成功,懂得查看 data/system/packages.xml 文件來得知應用的基礎信息,如 so 庫地址,primaryCpuAbi 等。
P3:掌握 System.load() 和 System.loadlibrary() 的區別。
P4:清楚系統尋找 so 文件的大體流程,知道系統什麼時候會去 system/lib 下載入 so 文件,什麼時候去 system/lib64。
P5:瞭解 ReLinker 和 SoLoder 庫的用途和大體原理。
正文
ps: 由於接觸尚淺,還看不懂源碼,正文部分大多數是直接從各大神博客中梳理出的結論,再用以自己的理解表達出來,因為並沒有結合源碼來分析,因此給出的結論觀點不保證百分百正確,如有錯誤,歡迎指點一下。
ps: 以下知識點梳理基於的設備系統 Android 5.1.1,api 22,不同系統的設備,也許過程會有些許差別。
1. install 過程
要瞭解 apk 的 install 過程都幹了哪些事,先要清楚一個 apk 文件中都有哪些東西,其實 apk 文件就是一個壓縮包,尾碼改為 zip 就可以直接打開查看內容了,或者 Android Studio 的 Analyze APK 功能也可以查看:
classes.dex 是源代碼,到時候要載入進記憶體運行在機器上的;lib 是存放 so 文件;res 是存放資源文件,包括佈局文件、圖片資源等等;assert 同樣存放一些資源文件;AndroidManifest.xml 是清單配置文件;
既然 apk 其實就是個壓縮包,將程式運行所需要的東西,比如源代碼,比如資源文件等等都打包在一起。那麼,install 的過程,其實也就是解壓&拷貝&解析的過程。
但不管是哪個過程,首先,這個 apk 文件得先傳送到終端設備上,所以,我們開發期間使用的 adb install
命令,或者是直接點擊 AndroidStudio 的 run 圖標指令(本質上也是運行的 adb install
),這個命令其實就幹了兩件事:
- adb push
- pm install
先將 apk push 到終端設備的臨時目錄,大多數場景下是:data/local/tmp
如果你有註意執行完 adb install
命令後,會先有一個百分比的進度,這個進度其實並不是安裝的進度,而是 adb push
的進度,你可以試著直接執行 adb push
命令,看一下是否會有一個進度提示。
先將 apk 從電腦上 push 到終端設備,然後調用 pm install
命令來安裝 apk。
調用了 pm install
命令後就會通知系統去安裝這個 apk 了,也就是上述說的拷貝、解壓、解析這幾個過程。
拷貝是因為,存放在 data/local/tmp 下的 apk 文件始終是位於臨時目錄下,隨時可能被刪掉,系統會先將這個 apk 拷貝一份到 data/app 目錄下。所以,在 data/app 這個目錄下,你基本可以看到所有三方 app 的 apk 包,如果三方 app 都沒有另外指定安裝到 SD 卡的話。
拷貝結束後,就是對這個 apk 文件進行解壓操作,獲取裡面的文件,將相關文件解壓到指定目錄,如:
- 創建 data/data/{包名} 目錄,存放應用運行期間所需的數據
- 掃描 apk 包中 lib 目錄的 so 文件結構,解壓到應用自身存放 so 庫的目錄,不同版本系統路徑有些不同,我設備的版本是 android 5.1.1,api 22,三方應用的 so 文件存放目錄就在 data/app/{包名}-1/lib 下
- class.dex 源代碼轉換成 odex 格式,緩存到 data/dalvik-cache 目錄下,加快應用的代碼運行效率
- 解析 AndroidManifext.xml 文件以及其他相關文件,將 app 的相關信息寫入 data/system/packages.xml 註冊表中
- 還有其他我不清楚的安裝工作
梳理一下,安裝 apk 過程中,就是解析 apk 中的內容,然後將不同作用的文件拷貝到指定目錄中待用,涉及的目錄有:
- data/data/{包名}
- data/dalvik-cache
- data/app/{包名}-1/lib (尾碼有可能是 -1,-2)
- data/system/packages.xml
我沒有找到存放 res,assert 這些資源文件的目錄,所以我猜想,這些資源文件其實並沒有解壓出來,仍舊是存放在 apk 中。我是這麼猜想的:
應用運行期間,類載入器所需的源代碼是從 data/dalvik-cache 緩存中載入,如果這裡沒有緩存,則去 data/app/ 對應的 apk 中解壓拿到 class.dex,轉換成 odex,再次緩存到 data/dalvik-cache,然後讓類載入器去載入。
而代碼運行期間所需的資料庫數據,xml 數據等則直接從 data/data/{包名} 中讀取,如果代碼需要 res 或 assert 資源文件,則再去 data/app 下的 apk 中拿取。
這是我的猜想,這也才能解釋,為什麼一旦將 data/app 下的 apk 刪掉,應用就無法運行,而如果將 data/data/{包名} 以及 data/dalvik-cache 緩存的 odex 源代碼文件刪掉,應用仍舊可以照常運行。
正確性與否不清楚,只是我的猜想,以後有時間翻閱源碼驗證一下。
小結一下
一個三方 apk 的安裝過程,不管是通過設備的有界面交互方式下的安裝,還是沒有交互界面直接通過 adb install
命令安裝,還是通過 Android Studio 的 run (本質上是執行 adb install 命令) 安裝。
這個過程,首先得先將 apk 文件傳送到終端設備上,設備上有了這個 apk 後,系統安裝應用的過程其實也就是先將這個 apk 拷貝一份到 data/app 目錄下,然後對其進行解壓工作,將 apk 包中的 so 文件解壓出來,將 dex 文件解壓之後對其進行優化處理緩存到 data/dalvik-cache 目錄,以便加快之後應用的運行,最後解析 AndroidManifext.xml 文件,將這個應用的基本信息寫入 data/system/packages.xml 文件中,然後創建 data/data/{包名} 目錄供應用運行期間存放數據。
2. 系統應用安裝
系統應用的安裝方式就不同於三方應用了,系統應用無法通過 install 命令來安裝,其實也可以說,adb install
安裝的都是三方應用,這些 apk 最後都被安裝到了 data/app 目錄下。
系統應用只能是在出 rom 包時集成,也就是你設備第一次買來開機時就已跟隨著 rom 包自帶的應用,除非你的應用有 root 許可權。這些應用可以升級,但升級後許可權會降為三方應用,將不在擁有系統許可權,但將升級後的刪掉,重啟,就又會恢復初始版本的系統應用了。
這是因為,系統應用的安裝過程基本都是在系統啟動時才去進行的。
常見的集成方式是直接將 apk 手動 push 到 system/app 目錄下,同時解壓出 apk 裡面的 so 文件,手動將其 push 到 system/lib 下(大部分場景,有的需要 push 到 system/lib64)。
當 push 完成時,如果是首次 push,那麼 data/system/packages.xml 註冊表中是沒有這個系統應用的任何信息的,所以需要重啟一下,才能夠運行這個應用。
系統在重啟的時候,會去掃描 system/app 目錄下的 apk 文件,如果發現這個 apk 沒有安裝,那麼會去觸發它的安裝工作。這也是為什麼重啟有時候會很耗時,尤其是升級完 rom 包後,因為此時需要安裝一些 apk。
而安裝過程基本跟三方應用一樣,只是因為 apk 已經在 system/app,所以不會將 apk 拷貝到 data/app。其餘的,優化 class.dex 格式為 odex 源代碼文件緩存到 data/dalvik-cache,寫配置到 data/system/packages.xml 中等等過程仍舊一樣。
但有一點,三方應用的 so 文件是直接解壓到 data/app 目錄下,但系統應用已不存在於 data/app 了,所以它並沒有解壓 so 文件這個過程,如果 apk 中有使用到 so 文件,那麼需要自己手動 push 到 system/lib 或者 system/lib64 目錄下。
當然,也可以另外一種集成方式:
- apk push 到 system/app/{自己創建的目錄}/
- so 文件 push 到 system/app/{自己創建的目錄}/lib 中
這種方式的說明,請看後面的後記一章節。
3. packages.xml
這份配置文件在 data/system/ 目錄下,不要小看這份文件,因為不管系統應用還是三方應用,安裝過程中都會將其自身的基本信息寫入這份文件中。所以,藉助這份文件,可以獲取到蠻多信息的。
比如,一般排查系統應用為什麼啟動不了,就可以藉助這份文件。
碰到過這麼一個問題,我們做的一些應用是沒有界面的,就純粹在後臺幹活。如果是三方,也許還可以通過手動去啟動這個應用來查看相關日誌,但偏偏還有些應用是設備開機時就自啟的,所以最怕遇到的問題就是測試人員跟你說,這個應用在某個終端上起不來。
因為這時,不清楚這個應用到底是不是因為代碼問題導致一直崩潰,起不來;還是因為根本就沒安裝成功;所以,遇到這類問題,第一點就是要先確認這一點,而確認這一點,就可以藉助 packages.xml 這份配置文件了。
如果能夠在這份 packages.xml 配置文件中找到應用的信息,那麼說明安裝成功了,接下去就往另一個方向排查問題了。
還有一種場景藉助這份配置文件分析也是很有幫助的。
我們還遇到這種情況:
首先 system/app 下是系統應用,data/app 下是三方應用,但系統是允許 system/app 和 data/app 下存在相同包名的應用,因為允許系統應用進行升級操作,只是此時系統應用將變成三方應用許可權。
某次,有反饋說,system/app 下已集成了最新版本的應用,但為什麼,每次啟動應用時,運行的都是舊版本。這時候怎麼排查,就是根據 packages.xml 中這個應用的基本信息,它包括,這個應用的版本號,apk 的來源目錄,so 文件的載入地址,所申請的許可權等等。
有了這些信息,足夠確認,此刻運行的應用是 data/app 下的 apk,還是 system/app 下的 apk。確認了之後,再進一步去排查。
4. System.load 和 System.loadlibrary
load()
和 loadlibrary()
都是用來載入 so 文件的,區別僅在於 load()
接收的是絕對路徑,比如 ”data/data/{包名}/lib/xxx.so“ 這樣子,因為是絕對路徑,所以最後跟著的是 so 文件全名,包括尾碼名。
而 loadlibrary()
只需傳入 so 文件去頭截尾的名字就可以了,如 libblur.so,只需傳入 blur 即可,內部會自動補全 so 文件可能存在的路徑,以及補全 lib 首碼和 .so 尾碼。
所以,下麵要講的,其實就是 loadlibrary()
載入 so 文件的流程。
因為之前碰到過這麼個問題,有些不大理解:
我們的應用是系統應用,那麼 so 文件也就是集成到 system/lib 或者 system/lib64 目錄下,但不清楚,程式是根據什麼決定是應該去 system/lib 目錄下載入 so 文件,還是去 system/lib64 下載入,或者兩處都會去?
所以,下個小節就是講這個。
5. so 文件載入流程
這節是本篇的重點,打算親自過下源碼來梳理,但這樣篇幅會特別長,基於此,就另起一篇來專門寫從源碼中梳理 so 文件的載入流程吧,這裡就只給出鏈接和幾點結論,感興趣的可以去看看。
- 一個應用在安裝過程中,系統會經過一系列複雜的邏輯確定兩個跟 so 文件載入相關的 app 屬性值:nativeLibraryDirectories ,primaryCpuAbi ;
- nativeLibraryDirectories 表示應用自身存放 so 文件的目錄地址,影響著 so 文件的載入流程;
- primaryCpuAbi 表示應用應該運行在哪種 abi 上,如(armeabi-v7a),它影響著應用是運行在 32 位還是 64 位的進程上,進而影響到尋找系統指定的 so 文件目錄的流程;
- 以上兩個屬性,在應用安裝結束後,可在 data/system/packages.xml 中查看;
- 當調用 System 的
loadLibrary()
載入 so 文件時,流程如下: - 先到 nativeLibraryDirectories 指向的目錄中尋找,是否存在且可用的 so 文件,有則直接載入這裡的 so 文件;
- 上一步沒找到的話,則根據當前進程如果是 32 位的,那麼依次去 vendor/lib 和 system/lib 目錄中尋找;
- 同樣,如果當前進程是 64 位的,那麼依次去 vendor/lib64 和 system/lib64 目錄中尋找;
- 當前應用是運行在 32 位還是 64 位的進程上,取決於系統的 ro.zygote 屬性和應用的 primaryCpuAbi 屬性值,系統的 ro.zygote 可通過執行 getprop 命令查看;
- 如果 ro.zygote 屬性為 zygote64_32,那麼應用啟動時,會先在 ro.product.cpu.abilist64 列表中尋找是否支持 primaryCpuAbi 屬性,有,則該應用運行在 64 位的進程上;
- 如果上一步不支持,那麼會在 ro.product.cpu.abilist32 列表中尋找是否支持 primaryCpuAbi 屬性,有,則該應用運行在 32 位的進程上;
- 如果 ro.zygote 屬性為 zygote32_64,則上述兩個步驟互換;
- 如果應用的 primaryCpuAbi 屬性為空,那麼以 ro.product.cpu.abilist 列表中第一個 abi 值作為應用的 primaryCpuAbi;
- 運行在 64 位的 abi 有:arm64-v8a,mips64,x86_64
- 運行在 32 位的 abi 有:armeabi-v7a,armeabi,mips,x86
- 通常支持 arm64-v8a 的 64 位設備,都會向下相容支持 32 位的 abi 運行;
- 但應用運行期間,不能混合著使用不同 abi 的 so 文件;
- 比如,當應用運行在 64 位進程中時,無法使用 32 位 abi 的 so 文件,同樣,應用運行在 32 位進程中時,也無法使用 64 位 abi 的 so 文件;
6. 三方庫 ReLinker 和 Soloder
ReLinker 和 Soloder 都是用於解決一些 so 文件載入失敗的場景,比如:
- 嵌套的 so 文件載入異常,如程式引用了三方庫,三方庫又引用了三方庫,各自庫中又都存在 so 文件載入,有時候可能會導致 so 文件載入失敗。
- so 文件缺失導致載入異常,如程式的 so 文件在設備的 so 目錄中不見了之類的異常。
- 等等
它們的 Github 地址:
SoLoader:https://github.com/facebook/SoLoader
ReLinker:https://github.com/KeepSafe/ReLinker
ReLinker 的原理我有去源碼梳理了一遍,大體上是這樣:
- 先調用系統
System.loadlibrary()
載入 so 文件,如果成功,結束; - 如果失敗,則重新解壓 apk 文件,解析其中的 lib 目錄,遍歷 so 文件,找到所需的 so 文件時,將其緩存一份至 data/data/{包名}/app-lib 目錄下,調用
System.load()
載入這份 so 文件; - 之後每次應用重啟,仍舊先調用系統的
System.loadlibrary()
去嘗試載入 so 文件,失敗,如果 app-lib 下有緩存,且可用,則載入這個緩存的 so 文件,否則重新解壓 apk,繼續 2 步驟。
- 當然,解壓 apk 遍歷 so 文件時,如果需要的 so 文件存在於不同的 CPU 架構目錄中,並不加以區分,直接拿第一個遍歷到的 so 文件。
SoLoder 的原理我只是稍微過了下,並沒有詳細看,因為我最後選擇的是 ReLinker 方案,但也可以大體上說一說:
- 遍歷設備所有存放 so 文件的目錄,如 system/lib, vendor/lib,緩存其中所有的 so 文件名。
- 如果系統載入 so 文件失敗時,則從緩存的所有 so 文件名列表中尋找是否有和當前要載入的 so 文件一致的,有則直接載入這個 so 文件。
原理大體上應該是這樣,感興趣可以自行去看一下。
那麼,這兩個 so 文件載入的開源庫有什麼用呢?看你是否有遇到過 so 文件載入異常了,我的應用場景在埋坑一節里細說。
埋坑
好了,理論基礎都已經有了,那麼接下去就是來埋坑了。
針對開頭所遇到的 bug,其實原因歸根結底就是沒有載入到正確的 so 文件,比如程式需要載入的是 system/lib64 下的 so 文件,但運維人員只集成到 system/lib 中;甚至說,運維人員連 so 文件都忘記集成到 system/lib 下了。
另外,運維人員希望,可以有一種統一的集成方法,他不需要去考慮是否還要根據其他條件來判斷他是否要集成到 system/lib 還是 system/lib64 還是兩者都要。
那麼,解決方案其實有兩種,一是給他一個新的無需考慮場景的集成方式;二是代碼層面做適配,動態去載入所缺失的或不能用的 so 文件。
方案一:系統應用集成方式
假設需要集成的應用包名:com.dasu.shuai,apk 文件名:dasu.apk
- 在 system/app 目錄下新建子目錄,命名能表示那個應用即可,如:dasu
- 將 dasu.apk push 到 system/app/dasu/ 目錄下
- 在 system/app/dasu 目錄下新建子目錄:lib/arm,這個命名是固定的,這樣系統才可以識別
- apk 編譯打包時,可以刪掉其他 CPU 架構的 so 文件,只保留 armeabi-v7a 即可(根據你們應用的用戶設備場景為主)
- 解壓 apk 文件,取出裡面的 lib/armeabi-v7a 下的 so 文件,push 到 system/lib 或 system/app/dasu/lib/arm 都可以
- 重啟(如果應用首次集成需重啟,否則 packages.xml 中無該應用的任何信息)
以上方案是針對我們應用自己的用戶群場景的集成方式,如果想要通用,最好註意一下步驟 3 和 4,上述的這兩個步驟目的在於讓系統將該應用的 primaryCpuAbi 屬性識別成 armeabi-v7a,這樣就無需編譯多個不同架構的 so 文件,集成也只需集成到 system/lib 目錄中即可。
系統在掃描到 lib/arm 有這個目錄存在時,會將 app 的 primaryCpuAbi 設置成 armeabi-v7a,相對應的,如果是 lib/arm64,那麼就設置成 arm64-v8a,這是在 api22 機子上測試的結果。
方案二:代碼適配
清楚了 ReLinker 的原理後,其實只要修改其中一個小小的流程即可。當系統載入 so 文件異常,ReLinker 接手來繼續尋找 so 文件時,進行到解壓 apk 包遍歷所有 so 文件時,如果有多個不同 CPU 架構的 so 文件,此時修改原本的以第一個遍歷到的 so 文件的邏輯,將其修改成尋找與此時應用的 primaryCpuAbi 一致的架構目錄下的 so 文件來使用。
我是兩種方案都做了,如果運維能夠按照正常步驟集成,那麼 so 文件載入異常的概率應該就不會大,即使運維哪個步驟操作失誤了,方案二也可以彌補。
後記
本來以為這樣子的解決方案足夠解決這個問題了,也達到了運維人員的需求了。但沒想到,事後居然又發現了新的問題:
由於我們是使用 fresco 圖片庫的,所以我們 app 的 so 文件其實都是來自 fresco 的,但沒想到,合作的廠商它們自己的 app 也是使用的 fresco,然後他們也需要集成 so 文件。
但由於都是作為系統應用集成,so 文件都是統一集成在同一個目錄中,如 system/lib,那麼我們使用的 fresco 的 so 文件肯定就跟他們的 so 文件衝突了,因為文件名都一致,最後集成的時候就只使用他們的 so 文件。
然後,我們使用的 fresco 版本還跟他們不一樣,結果就導致了,使用他們的 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 官網是否有給出相關的文章。一般來說,新版本應該能相容舊版本才對,這也就意味著,我們使用的版本其實比合作方他們新,如果集成時,使用的是我們的 so 文件,雙方應該就都沒問題。但跟他們合作一起集成時,如何來判斷誰使用的版本新,誰的舊?都不更新的嗎?
畢竟人家是廠商,我們只是需求合作,我們弱勢,那還是我們自己再來想解決方案吧。
原本的 ReLinker 方案只能解決 so 文件不存在,載入失敗,或者 so 文件 abi 異常的問題,但解決不了,so 文件的版本更新問題。
如果真要從代碼層面著手,也不是不行,每次載入 so 文件前,先手動去系統的 so 文件目錄中,將即將要載入的 so 文件進行一次 md5 計算,程式中可以保存打包時使用的 so 文件的 md5 值,兩者相互比較,來判斷 so 文件對應的代碼版本是否一致。但這樣會導致正常的流程需要額外處理一些耗時工作,自行評估吧。
或者,讓運維人員在集成時,乾脆不要將 so 文件集成到 system/lib 目錄中,直接集成到 system/app/{新建目錄}/lib/arm/ 目錄下,這樣我們就只使用我們自己的 so 文件,不用去擔心跟他們共用時,版本差異問題了。
參考資料
2.Android程式包管理(2)--使用adb install執行安裝過程
大家好,我是 dasu,歡迎關註我的公眾號(dasuAndroidTv),如果你覺得本篇內容有幫助到你,可以轉載但記得要關註,要標明原文哦,謝謝支持~