1. 程式人生 > >系統應用整合過程中的一些坑

系統應用整合過程中的一些坑

這次想來講講系統應用整合過程中遇到的一些坑,尤其是 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

cpu.png

果然,這個機子支援的 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的包名}

packages.png

後來有些疑惑,這裡的 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 裡嘗試尋找了下,結果發現,哈哈哈,原來這麼多人碰到過這個問題:

issus.png

要相信,你絕對不是第一個遇到問題的人。是吧,這麼多人都來這裡提問了,開源庫的負責人肯定給出解決方案了,所以接下去繼續在這些 issues 裡過濾一下,找出那些跟你一樣的問題就可以了。如下面這篇:

issue.png

官方人員已經說了,可以嘗試使用 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 功能也可以檢視:

apk結構.png

classes.dex 是原始碼,到時候要載入進記憶體執行在機器上的;lib 是存放 so 檔案;res 是存放資原始檔,包括佈局檔案、圖片資源等等;assert 同樣存放一些資原始檔;AndroidManifest.xml 是清單配置檔案;

既然 apk 其實就是個壓縮包,將程式執行所需要的東西,比如原始碼,比如資原始檔等等都打包在一起。那麼,install 的過程,其實也就是解壓&拷貝&解析的過程。

但不管是哪個過程,首先,這個 apk 檔案得先傳送到終端裝置上,所以,我們開發期間使用的 adb install 命令,或者是直接點選 AndroidStudio 的 run 圖示指令(本質上也是執行的 adb install),這個命令其實就幹了兩件事:

  1. adb push
  2. pm install

先將 apk push 到終端裝置的臨時目錄,大多數場景下是:data/local/tmp

adbinstall.png

如果你有注意執行完 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 地址:

ReLinker 的原理我有去原始碼梳理了一遍,大體上是這樣:

  1. 先呼叫系統 System.loadlibrary() 載入 so 檔案,如果成功,結束;
  2. 如果失敗,則重新解壓 apk 檔案,解析其中的 lib 目錄,遍歷 so 檔案,找到所需的 so 檔案時,將其快取一份至 data/data/{包名}/app-lib 目錄下,呼叫 System.load() 載入這份 so 檔案;
  3. 之後每次應用重啟,仍舊先呼叫系統的 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

  1. 在 system/app 目錄下新建子目錄,命名能表示那個應用即可,如:dasu
  2. 將 dasu.apk push 到 system/app/dasu/ 目錄下
  3. 在 system/app/dasu 目錄下新建子目錄:lib/arm,這個命名是固定的,這樣系統才可以識別
  4. apk 編譯打包時,可以刪掉其他 CPU 架構的 so 檔案,只保留 armeabi-v7a 即可(根據你們應用的使用者裝置場景為主)
  5. 解壓 apk 檔案,取出裡面的 lib/armeabi-v7a 下的 so 檔案,push 到 system/lib 或 system/app/dasu/lib/arm 都可以
  6. 重啟(如果應用首次整合需重啟,否則 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 檔案,不用去擔心跟他們共用時,版本差異問題了。

參考資料

大家好,我是 dasu,歡迎關注我的公眾號(dasuAndroidTv),如果你覺得本篇內容有幫助到你,可以轉載但記得要關注,要標明原文哦,謝謝支援~ dasuAndroidTv2.png