Android開發中libs目錄下so文件的正確放置「姿式」

0X0 前言java

Android 系統中,當咱們安裝apk文件的時候,lib 目錄下的 so 文件會被解壓到 app 的原生庫目錄,通常來講是放到 /data/data/<package-name>/lib 目錄下,而根據系統和CPU架構的不一樣,其拷貝策略也是不同的,在咱們測試過程當中發現不正確地配置了 so 文件,好比某些 app 使用第三方的 so 時,只配置了其中某一種 CPU 架構的 so,可能會形成 app 在某些機型上的適配問題。因此這篇文章主要介紹一下在不一樣版本的 Android 系統中,安裝 apk 時,PackageManagerService 選擇解壓 so 庫的策略,並給出一些 so 文件配置的建議。架構

0x1 Android4.0之前app

apk 被安裝時,執行路徑雖然有差異,但最終要調用到的一個核心函數是 copyApk,負責拷貝 apk 中的資源。ide

參考2.3.6 Android 源碼,它的 copyApk 其內部函數一段選取原生庫 so 邏輯:函數

public static int listPackageNativeBinariesLI(ZipFile zipFile, List> nativeFiles) throws ZipException, IOException {
  
 }

篇幅有限,這裏還有下面全部的源代碼沒法展現出來,若是要查看完整源碼的版本,請戳這裏性能


這段代碼中的 Build.CPU_ABI "ro.product.cpu.abi2" 分別爲手機支持的主 abi 和次 abi 屬性字符串,abi 爲手機支持的指令集所表明的字符串,好比 armeabi-v7aarmeabix86mips 等,而主 abi 和次 abi 分別表示手機支持的第一指令集和第二指令集。代碼首先調用 listPackageSharedLibsForAbiLI 來遍歷主 abi 目錄。當主 abi 目錄不存在時,纔會接着調用 listPackageSharedLibsForAbiLI 遍歷次 abi 目錄。測試

private static int listPackageSharedLibsForAbiLI(ZipFile zipFile, String cpuAbi, List> libEntries) throws IOException, ZipException {
 }


listPackageSharedLibsForAbiLI 中判斷當前遍歷的 apk 中文件的 entry 名是否符合 so 命名的規範且含相應 abi 字符串名。若是符合則規則則將 so entry 名加入 list,若是遍歷失敗或者規則不匹配則返回相應錯誤碼。ui

拷貝 so 策略:spa

遍歷 apk 中文件,當 apk lib 目錄下主 abi 子目錄中有 so 文件存在時,則所有拷貝主 abi 子目錄下的 so;只有當主 abi 子目錄下沒有 so 文件的時候即 PACKAGE_INSTALL_NATIVE_ABI_MISMATCH 的狀況,纔會拷貝次 ABI 子目錄下的 so 文件。code

策略問題:

so 放置不當時,安裝 apk 時會致使拷貝不全。好比 apk lib 目錄下存在 armeabi/libx.so , armeabi/liby.so , armeabi-v7a/libx.so 3 so 文件,那麼在主 ABI armeabi-v7a 且系統版本小於4.0的手機上, apk 安裝後,按照拷貝策略,只會拷貝主 abi 目錄下的文件即 armeabi-v7a/libx.so,當加載 liby.so 時就會報找不到 so 的異常。另外若是主 abi 目錄不存在,這個策略會遍歷2 apk,效率偏低。

0x2 Android 4.0-Android 4.0.3

參考4.0.3 Android 源碼,同理,找處處理 so 拷貝的核心邏輯( native 層):

static install_status_t iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, iterFunc callFunc, void* callArg) {
     ...
 }


拷貝 so 策略:

遍歷 apk 中全部文件,若是符合 so 文件的規則,且爲主 ABI 目錄或者次 ABI 目錄下的 so,就解壓拷貝到相應目錄。

策略問題:

存在同名 so覆蓋,好比一個 app armeabi armeabi-v7a 目錄下都含同名的 so,那麼就會發生覆蓋現象,覆蓋的前後順序根據 so 文件對應 ZipFileR0 中的 hash 值而定,考慮這樣一個例子,假設一個 apk 同時有 armeabi/libx.so armeabi-v7a/libx.so,安裝到主 ABI armeabi-v7a 的手機上,拷貝 so 時根據遍歷順序,存在一種可能即 armeab-v7a/libx.so 優先遍歷並被拷貝,隨後 armeabi/libx.so 被遍歷拷貝,覆蓋了前者。原本應該加載 armeabi-v7a 目錄下的 so,結果按照這個策略拷貝了 armeabi 目錄下的 so

apk 中文件 entry 的散列計算函數以下:

unsigned int ZipFileRO::computeHash(const char* str, int len)
 {   
 }


0x3 Android 4.0.4之後

4.1.2系統爲例,遍歷選擇 so 邏輯以下:

static install_status_t iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, iterFunc callFunc, void* callArg) {
     
 }


拷貝 so 策略:

遍歷 apk 中文件,當遍歷到有主 Abi 目錄的 so 時,拷貝並設置標記 hasPrimaryAbi 爲真,之後遍歷則只拷貝主 Abi 目錄下的 so,當標記爲假的時候,若是遍歷的 so entry 名含次 abi 字符串,則拷貝該 so

策略問題:

通過實際測試, so 放置不當時,安裝 apk 時存在 so 拷貝不全的狀況。這個策略想解決的問題是在 4.0 ~ 4.0.3 系統中的 so 隨意覆蓋的問題,即若是有主 abi 目錄的 so 則拷貝,若是主 abi 目錄不存在這個 so 則拷貝次 abi 目錄的 so,但代碼邏輯是根據 ZipFileR0 的遍歷順序來決定是否拷貝 so,假設存在這樣的 apk lib 目錄下存在 armeabi/libx.so , armeabi/liby.so , armeabi-v7a/libx.so 這三個 so 文件,且 hash 的順序爲 armeabi-v7a/libx.so armeabi/liby.so 以前,則 apk 安裝的時候 liby.so 根本不會被拷貝,由於按照拷貝策略, armeabi-v7a/libx.so 會優先遍歷到,因爲它是主 abi 目錄的 so 文件,因此標記被設置了,當遍歷到 armeabi/liby.so 時,因爲標記被設置爲真, liby.so 的拷貝就被忽略了,從而在加載 liby.so 的時候會報異常。

0x4 64位系統支持

Android 5.0以後支持64 ABI,以5.1.0系統爲例:

public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot, String abiOverride) {   
 }


copyNativeBinariesWithOverride 分別處理32位和64 so 的拷貝,內部函數 copyNativeBinariesForSupportedAbi 首先會根據 abilist 去找對應的 abi

public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, String[] abiList, boolean useIsaSubdir) throws IOException {
    
 }


findSupportedAbi 內部實現是 native 函數,首先遍歷 apk,若是 so 的全路徑中含 abilist 中的 abi 字符串,則記錄該 abi 字符串的索引,最終返回全部記錄索引中最靠前的,即排在 abilist 中最前面的索引。

static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {   
 }


舉例說明,在某64位測試手機上的abi屬性顯示以下,它有2 abilist,分別對應該手機支持的32位和64 abi 的字符串組。

當處理32 so 拷貝時, findSupportedAbi 索引返回以後,若返回爲0,則拷貝 armeabi-v7a 目錄下的 so,若是爲1,則拷貝 armeabi 目錄下 so

拷貝 so 策略:

分別處理32位和64 abi 目錄的 so 拷貝, abi 由遍歷 apk 結果的全部 so 中符合 abilist 列表的最靠前的序號決定,而後拷貝該 abi 目錄下的 so 文件。

策略問題:

策略假定每一個 abi 目錄下的 so 都放置徹底的,這是和2.3.6同樣的處理邏輯,存在遺漏拷貝 so 的可能。

0x5 建議

針對 Android 系統的這些拷貝策略的問題,咱們給出了一些配置 so 的建議:

1.    1)針對 armeabi armeabi-v7a 兩種 ABI

方法1:因爲 armeabi-v7a 指令集兼容 armeabi 指令集,因此若是損失一些應用的性能是能夠接受的,同時不但願保留庫的兩份拷貝,能夠移除 armeabi-v7a 目錄和其下的庫文件,只保留 armeabi 目錄;好比 apk 使用第三方的 so 只有 armeabi 這一種 abi 時,能夠考慮去掉 apk lib 目錄下 armeabi-v7a 目錄。

方法2:在 armeabi armeabi-v7a 目錄下各放入一份 so

2.    2)針對x86

目前市面上的x86機型,爲了兼容 arm 指令,基本都內置了 libhoudini 模塊,即二進制轉碼支持,該模塊負責把 ARM 指令轉換爲 X86 指令,因此若是是出於 apk package大小的考慮,而且能夠接受一些性能損失,能夠選擇刪掉 x86 庫目錄, x86 下配置的 armeabi 目錄的 so 庫同樣能夠正常加載使用;

3.    3)針對64 ABI

若是 app 開發者打算支持64位,那麼64位的 so 要放全,不然能夠選擇不單獨編譯64位的 so,所有使用32位的 so64位機型默認支持32 so 的加載。好比 apk 使用第三方的 so 只有32 abi so,能夠考慮去掉 apk lib 目錄下的64 abi 子目錄,保證 apk 安裝後正常使用。

0x6 備註

其實本文是由於在 Android so 加載上遇到不少坑,相信不少朋友都遇到過 UnsatisfiedLinkError 這個錯誤,反應在用戶的機型上也是千差萬別,可是有沒有想過,可能不是 apk 邏輯的問題,而是 Android 系統在安裝 APK 的時候,因爲 PackageManager 的問題,並無拷貝相應的 SO 呢?能夠參考下面第4個連接,做者給出瞭解決方案,就是當出現 UnsatisfiedLinkError 錯誤時,手動拷貝 so 來解決的。

因爲oschina篇幅有限,更詳細文章請點擊原文Android 系統安裝 apk 時解壓 so 的邏輯問題

相關文章
相關標籤/搜索