Android6.0,本地庫(Native Library)的選擇流程

什么是Native Library?

Native Library,一般我們譯為本地庫或原生庫,是由C/C++編寫的動態(tài)庫(.so),并通過JNI(Java Native Interface)機制為java層提供接口。應用一般會出于性能、安全等角度考慮將相關(guān)邏輯用C/C++實現(xiàn)并編譯為庫的形式提供接口,供上層或其他模塊調(diào)用。

為什么需要本地庫(Native Library)的選擇?

我們知道本地庫(Native Library)是由C/C++源碼編譯后的動態(tài)庫文件,其編譯是C/C++源碼根據(jù)其要運行的目標平臺的指令集翻譯成對應的機器碼的過程,不同的處理器架構(gòu),需要編譯出相應平臺的動態(tài)庫,才能被正確的執(zhí)行。因此為了讓app能在不同的處理器平臺上都能正確的運行,一般app都會將其C/C++源文件為常見平臺(armeabi,armeabi-v7a等)都編譯出相應的本地庫文件,在打包成apk文件時,將這些不同平臺的庫都打包進apk的lib子目錄中,每一種abi存放一個目錄,目錄名為abi的類型名。因此,一個apk文件的lib子目錄中就可能同時包含多種abi庫文件情況,當一個用戶在自己的設備上安裝該app時,系統(tǒng)就需要根據(jù)自己的所支持的abi類型,為app選擇適當abi類型的庫文件進行安裝,以確保app在系統(tǒng)上正常運行。

Android6.0,本地庫(Native Library)的選擇流程

上文提到本地庫(Native Library)的選擇流程是在app被安裝的時候進行的,下面就以此為切入點,來看看在android6.0安裝app時,是怎么為其選擇合適的本地庫(Native Library)的。

首先,我們來看一下PMS(PackageManagerService)scanPackageDirtyLI函數(shù),它是負責app安裝時簽名驗證、本地庫(Native Library)的選擇安裝等系列工作的,這里我們重點看一下本地庫(Native Library)的選擇流程:

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throw PackageManagerException{
    ....
    /*計算cpuOverride的值,pkg.cpuAbiOverride來自安裝參數(shù)指定,
    pkg.cpuAbiOverrided的優(yōu)先級高于pkgSetting.cpuAbiOverrideString,
    只要pkg.cpuAbiOverrided不等于NativeLibraryHelper.CLEAR_ABI_OVERRIDE
    */
    final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
    if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
    /* 這里執(zhí)行abi的選擇和本地庫的安裝操作,最后一個參數(shù)表示是否要將本地庫抽取出來,這里為true,表示需要*/
        derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
    }else{
        ....
    }
    ....
}

可以看到在scanPackageDirtyLI中主要是調(diào)用了derivePackageAbi執(zhí)行abi的選擇和安裝操作,接下來我們看一下 derivePackageAbi的實現(xiàn):
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
                             String cpuAbiOverride, boolean extractLibs)
        throws PackageManagerException {
    setNativeLibraryPaths(pkg);//主要是初始化和設置與本地相關(guān)的幾個變量,如本地庫的目錄等
    ....
    NativeLibraryHelper.Handle handle = null;
    try {
        handle = NativeLibraryHelper.Handle.create(scanFile); //打開apk文件
        final File nativeLibraryRoot = new File(nativeLibraryRootStr);
        // 將primaryCpuAbi和secondaryCpui都先置為null,以便于后面重新計算abi
        pkg.applicationInfo.primaryCpuAbi = null;
        pkg.applicationInfo.secondaryCpuAbi = null;
        if (isMultiArch(pkg.applicationInfo)) {
        // pkg.applicationInfo.pkgFlags設置了FLAG_MULTIARCH標志位,
        //即表示該app的代碼會被其他app進程加載,因此就可能需要同時安裝32位和64位的本地庫
            // Warn if we've set an abiOverride for multi-lib packages..
            // By definition, we need to copy both 32 and 64 bit libraries for
            // such packages.
            ...
            int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
            int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
            if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
            //系統(tǒng)支持32位的abi
                if (extractLibs) {
                //表示需要為該app選擇、**安裝**合適的abi
                    abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                            nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                            useIsaSpecificSubdirs);//調(diào)用NativeLibraryHelp的copyNativeBinariesForSupportedAbi進行abi的選擇和安裝
                } else {
                //只需選擇(計算)出合適的abi即可,**不用執(zhí)行安裝**操作
                    abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);//調(diào)用NativeLibraryHelp的findSupportedAbi進行abi的選擇 --- 這是本文關(guān)注的重點
                }
            }
            maybeThrowExceptionForMultiArchCopy(
                    "Error unpackaging 32 bit native libs for multiarch app.", abi32);
            //系統(tǒng)支持64位的abi。流程和32位abi選擇流程相同,只是將調(diào)用參數(shù)中的候選abi列表改為了64位的,
            //即Build.SUPPORTED_32_BIT_ABIS改為Build.SUPPORTED_64_BIT_ABIS
            if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                if (extractLibs) {
                    abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                            nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
                            useIsaSpecificSubdirs);
                } else {
                    abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
                }
            }
            maybeThrowExceptionForMultiArchCopy(
                    "Error unpackaging 64 bit native libs for multiarch app.", abi64);

            //根據(jù)計算結(jié)果,對primaryCpuAbi和secondaryCpuAbi進行賦值,注意這里查找結(jié)果abi32,abi64分別是在Build.SUPPORTED_32_BIT_ABIS和Build.SUPPORTED_64_BIT_ABIS列表中的下標
            /*賦值的規(guī)則如下:

            1. 如果同時找到64位和32位的abi,則primaryCpuAbi設為64位的,secondaryCpuAbi設為32位的
            2. 只找到64位但是沒有找到32位的abi,則primaryCpuAbi設為64位的,secondaryCpuAbi保持為null
            3. 如果沒有找到64位但是找到了32位合適的abi,primaryCpuAbi設為32為的abi,secondaryCpuabi保持為null
            4. 64位和32位的abi都沒找到,則primaryCpuAbi和secondaryCpuAbi都保持為null
            */
            if (abi64 >= 0) {
                pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
            }
            if (abi32 >= 0) {
                final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                if (abi64 >= 0) {
                    pkg.applicationInfo.secondaryCpuAbi = abi;
                } else {
                    pkg.applicationInfo.primaryCpuAbi = abi;
                }
            }
        } else {
        //app的代碼不會被其他app進程載入
            /*
            初始化候選abi列表:abiList。cpuAbiOverride優(yōu)先級高于Build.SUPPORTED_ABIS,
            前者是根據(jù)安裝參數(shù)和PackageSetting計算出來的,后者是系統(tǒng)編譯時生成的
            */
            String[] abiList = (cpuAbiOverride != null) ?
                    new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
            ...
            //以下邏輯與if語句中的邏輯相同,copyRet存儲合適的abi在候選abi列表(abiList)中的下標
            final int copyRet;
            if (extractLibs) {
                copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                        nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
            } else {
                copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
            }
            if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Error unpackaging native libs for app, errorCode=" + copyRet);
            }
            //對primaryCpuAbi進行賦值
            if (copyRet >= 0) {
                pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
            } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
            //NativeLibraryHelper沒找到合適的abi,但是cpuAbiOverride不為null,則primaryCpuAbi設為cpuAbiOverride
                pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
            } else if (needsRenderScriptOverride) {
                pkg.applicationInfo.primaryCpuAbi = abiList[0];
            }
        }
    } catch (IOException ioe) {
        Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
    } finally {
        //關(guān)閉apk文件
        IoUtils.closeQuietly(handle);
    }
    // 計算出本地庫,再一次更新本地庫目錄及其相關(guān)信息
    setNativeLibraryPaths(pkg);
}

接下來我們就到NativeLibraryHelperfindSupportedAbi中看看,具體是怎么進行abi選擇的:
frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
    int finalRes = NO_NATIVE_LIBRARIES;
    /*handle為上文中PMS中derivePackageAbi調(diào)用NativeLibraryHelper.Handle.create
    * 打開的。因為存在Split APK機制,一個app有可能被拆分成幾個apk情況,
    * create函數(shù)會打開一個app所有的apk文件,并存放在apkHandles數(shù)組中
    */
    //依次處理一個app的每一個apk文件
    for (long apkHandle : handle.apkHandles) {
        //直接調(diào)用nativeFindSupportedAbi對每一個apk進行本地庫的選擇
        final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
        if (res == NO_NATIVE_LIBRARIES) {
            // 這個apk不存在本地庫,什么也不做
        } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
            //apk存在本地庫,但是不在系統(tǒng)支持的列表中,
            //將返回值暫時先置為INSTALL_FAILED_NO_MATCHING_ABIS
            if (finalRes < 0) {
                finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
            }
        } else if (res >= 0) {
            // apk存在本地庫,且在系統(tǒng)支持的列表中,
            // 如果其在系統(tǒng)支持列表的索引比之前apk找到的低,則更新返回值
            if (finalRes < 0 || res < finalRes) {
                finalRes = res;
            }
        } else {
            // 未知錯誤,直接結(jié)束,返回相應的錯誤值
            return res;
        }
    }
    return finalRes;
}

總的來說findSupportedAbi還是比較簡單,依次對一個app的所有apk調(diào)用nativeFindSupportedAbi來選擇合適的本地庫。 如果多個apk文件都找到了本地庫,則根據(jù)其在supportedAbis索引值,選擇索引較小的。因此可以看出系統(tǒng)支持的本地庫優(yōu)先級,在supportedAbis中是按高到低依次排列的。接下來我們就來看一下,對每一個apk進行本地庫選擇的nativeFindSupportedAbi

NativeLibraryHelper中,nativeFindSupportedAbi的定義如下

private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);

是一個native函數(shù),其定義在:
frameworks/base/core/java/jni/com_android_internal_content_NativeLibraryHelper.cpp

其中JNINativeMethod定義如下:

static JNINativeMethod gMethods[] = {
{
...
{"nativeFindSupportedAbi",
        "(J[Ljava/lang/String;)I",
        (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
...
};

可見NativeLibraryHelper.java中調(diào)用的nativeFindSupportedAbicom_android_internal_content_NativeLibraryHelper.cpp中定義的函數(shù)名為com_android_internal_content_NativeLibraryHelper_findSupportedAbi,其定義如下:

static jint
com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass   clazz, jlong apkHandle, jobjectArray javaCpuAbisToSearch)
{
    return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
}

該函數(shù)只是簡單的調(diào)用findSupportedAbi,本身沒有做工作。我們接著看findSupportedAbi的實現(xiàn):

static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
    /*
    * 先將java層的傳入的系統(tǒng)支持abi類型列表(String[])轉(zhuǎn)化為C++層的表示(VectorM<SCopedUtfChars),這樣做的目的應該是為了便于后續(xù)字符串比較之類的操作更加方便。
    */
    const int numAbis = env->GetArrayLength(supportedAbisArray);
    Vector<ScopedUtfChars*> supportedAbis;
    for (int i = 0; i < numAbis; ++i) {
        supportedAbis.add(new ScopedUtfChars(env,
            (jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
    }
    //將java層NativeLibraryHelp.java中findSupportedAbi傳入的apkHandle(long)轉(zhuǎn)化為ZipFileRO*
    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

    /*  創(chuàng)建NativeLibrariesIterator迭代器,該迭代器主要做了以下封裝:
    * 1. 在打開的時候會自動打開apk文件的lib目錄
    * 2. next操作會自動迭代apk文件lib目錄中的本地庫文件,自動跳過非本地庫文件,并更新相關(guān)變量。
    */
    UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
    if (it.get() == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }
    ZipEntryRO entry = NULL;
    int status = NO_NATIVE_LIBRARIES;
    //通過迭代器的next函數(shù),依次迭代lib目錄下的本地庫文件
    while ((entry = it->next()) != NULL) {
        //找到了本地庫,且初始化后未被修改過,則將返回值暫且設為INSTALL_FAILED_NO_MATCHING_ABIS
        if (status == NO_NATIVE_LIBRARIES) {
            status = INSTALL_FAILED_NO_MATCHING_ABIS;
        }
        const char* fileName = it->currentEntry();//文件名,包括相對路徑的目錄名,如lib/armeabi/libxx.so
        const char* lastSlash = it->lastSlash();//路徑中,最后一個`/`的位置
        // 分別獲取子目錄名的起始地址和目錄名長度-->這個目錄名就是這個本地庫文件的abi類型
        const char* abiOffset = fileName + APK_LIB_LEN;
        const size_t abiSize = lastSlash - abiOffset;
        //檢查候選abi列表是否包含該abi類型
        for (int i = 0; i < numAbis; i++) {
            const ScopedUtfChars* abi = supportedAbis[i];
            if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
                //該本地庫文件的abi類型在候選abi列表中
                if (((i < status) && (status >= 0)) || (status < 0) ) {
                //該abi類型在候選abi列表的索引小于已經(jīng)找到的值或者之前還未找到合適的abi類型,則更新返回值
                    status = i;
                }
            }
        }
    }
    //回收內(nèi)存
    for (int i = 0; i < numAbis; ++i) {
        delete supportedAbis[i];
    }
    return status;
}

可以看到,真正的本地庫的abi類型選擇是在native層完成的,核心邏輯其實也挺簡單:即查看apk文件中l(wèi)ib子目錄,看其包含了哪些子目錄,每個目錄名即一種abi類型;再查看這些abi類型是否在系統(tǒng)支持的abi列表改中,如果在則該abi就是app在系統(tǒng)中將要使用的abi類型,如果同時存在多種類型滿足的情況,則選取索引在候選abi列表中較小的那個。

總結(jié)一下要點

  • 如果app設置了FLAG_MULTIARCH標志位(即,app要被載入其他app進程), 需要同時選擇64位和32位的abi,并根據(jù)結(jié)果,分別對primaryCpuAbisecondaryCpuAbi賦值;否則,判斷一下安裝時是否指定了abi類型,如果指定了,就以該值作為abi候選列表,沒有指定就用Build.SUPPORTED_ABIS(它是一個String[]類型,每個元素是一種abi類型的名稱,如 armeabi,armeabi-V7A,arm64-V8A,X86等),在系統(tǒng)編譯時確定的。
  • apk文件是zip文件類型,本地庫文件就放在它的lib/子目錄下,每一種abi類型的本地庫文件存放一個目錄,目錄名就是該abi類型名。
  • 一個app可能拆分成幾個apk文件組成。在為app進行abi類型選擇時,依次查看它每一個apk文件,選出最合適abi類型,如果一個app不同apk文件選出的abi類型不同,則為app選取在候選abi列表中索引最小的那個。apk文件的最合適abi類型的標準是:該abi類型在abi候選列表中,且索引最小。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容