Android6.0,本地库(Native Library)的选择流程

什么是Native Library?

Native Library,一般我们译为本地库或原生库,是由C/C++编写的动态库(.so),并通过JNI(Java Native Interface)机制为java层提供接口。应用一般会出于性能、安全等角度考虑将相关逻辑用C/C++实现并编译为库的形式提供接口,供上层或其他模块调用。

为什么需要本地库(Native Library)的选择?

我们知道本地库(Native Library)是由C/C++源码编译后的动态库文件,其编译是C/C++源码根据其要运行的目标平台的指令集翻译成对应的机器码的过程,不同的处理器架构,需要编译出相应平台的动态库,才能被正确的执行。因此为了让app能在不同的处理器平台上都能正确的运行,一般app都会将其C/C++源文件为常见平台(armeabiarmeabi-v7a等)都编译出相应的本地库文件,在打包成apk文件时,将这些不同平台的库都打包进apk的lib子目录中,每一种abi存放一个目录,目录名为abi的类型名。因此,一个apk文件的lib子目录中就可能同时包含多种abi库文件情况,当一个用户在自己的设备上安装该app时,系统就需要根据自己的所支持的abi类型,为app选择适当abi类型的库文件进行安装,以确保app在系统上正常运行。

Android6.0,本地库(Native Library)的选择流程

上文提到本地库(Native Library)的选择流程是在app被安装的时候进行的,下面就以此为切入点,来看看在android6.0安装app时,是怎么为其选择合适的本地库(Native Library)的。

首先,我们来看一下PMS(PackageManagerService)scanPackageDirtyLI函数,它是负责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来自安装参数指定,
    pkg.cpuAbiOverrided的优先级高于pkgSetting.cpuAbiOverrideString,
    只要pkg.cpuAbiOverrided不等于NativeLibraryHelper.CLEAR_ABI_OVERRIDE
    */
    final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
    if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
    /* 这里执行abi的选择和本地库的安装操作,最后一个参数表示是否要将本地库抽取出来,这里为true,表示需要*/
        derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
    }else{
        ....
    }
    ....
}

可以看到在scanPackageDirtyLI中主要是调用了derivePackageAbi执行abi的选择和安装操作,接下来我们看一下 derivePackageAbi的实现:
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);//主要是初始化和设置与本地相关的几个变量,如本地库的目录等
    ....
    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) {
            //系统支持32位的abi
                if (extractLibs) {
                //表示需要为该app选择、**安装**合适的abi
                    abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                            nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                            useIsaSpecificSubdirs);//调用NativeLibraryHelp的copyNativeBinariesForSupportedAbi进行abi的选择和安装
                } else {
                //只需选择(计算)出合适的abi即可,**不用执行安装**操作
                    abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);//调用NativeLibraryHelp的findSupportedAbi进行abi的选择 --- 这是本文关注的重点
                }
            }
            maybeThrowExceptionForMultiArchCopy(
                    "Error unpackaging 32 bit native libs for multiarch app.", abi32);
            //系统支持64位的abi。流程和32位abi选择流程相同,只是将调用参数中的候选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);

            //根据计算结果,对primaryCpuAbi和secondaryCpuAbi进行赋值,注意这里查找结果abi32,abi64分别是在Build.SUPPORTED_32_BIT_ABIS和Build.SUPPORTED_64_BIT_ABIS列表中的下标
            /*赋值的规则如下:

            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优先级高于Build.SUPPORTED_ABIS,
            前者是根据安装参数和PackageSetting计算出来的,后者是系统编译时生成的
            */
            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 {
        //关闭apk文件
        IoUtils.closeQuietly(handle);
    }
    // 计算出本地库,再一次更新本地库目录及其相关信息
    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调用NativeLibraryHelper.Handle.create
    * 打开的。因为存在Split APK机制,一个app有可能被拆分成几个apk情况,
    * create函数会打开一个app所有的apk文件,并存放在apkHandles数组中
    */
    //依次处理一个app的每一个apk文件
    for (long apkHandle : handle.apkHandles) {
        //直接调用nativeFindSupportedAbi对每一个apk进行本地库的选择
        final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
        if (res == NO_NATIVE_LIBRARIES) {
            // 这个apk不存在本地库,什么也不做
        } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
            //apk存在本地库,但是不在系统支持的列表中,
            //将返回值暂时先置为INSTALL_FAILED_NO_MATCHING_ABIS
            if (finalRes < 0) {
                finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
            }
        } else if (res >= 0) {
            // apk存在本地库,且在系统支持的列表中,
            // 如果其在系统支持列表的索引比之前apk找到的低,则更新返回值
            if (finalRes < 0 || res < finalRes) {
                finalRes = res;
            }
        } else {
            // 未知错误,直接结束,返回相应的错误值
            return res;
        }
    }
    return finalRes;
}

总的来说findSupportedAbi还是比较简单,依次对一个app的所有apk调用nativeFindSupportedAbi来选择合适的本地库。 如果多个apk文件都找到了本地库,则根据其在supportedAbis索引值,选择索引较小的。因此可以看出系统支持的本地库优先级,在supportedAbis中是按高到低依次排列的。接下来我们就来看一下,对每一个apk进行本地库选择的nativeFindSupportedAbi

NativeLibraryHelper中,nativeFindSupportedAbi的定义如下

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

是一个native函数,其定义在:
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中调用的nativeFindSupportedAbicom_android_internal_content_NativeLibraryHelper.cpp中定义的函数名为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);
}

该函数只是简单的调用findSupportedAbi,本身没有做工作。我们接着看findSupportedAbi的实现:

static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
    /*
    * 先将java层的传入的系统支持abi类型列表(String[])转化为C++层的表示(VectorM<SCopedUtfChars),这样做的目的应该是为了便于后续字符串比较之类的操作更加方便。
    */
    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)转化为ZipFileRO*
    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }

    /*  创建NativeLibrariesIterator迭代器,该迭代器主要做了以下封装:
    * 1. 在打开的时候会自动打开apk文件的lib目录
    * 2. next操作会自动迭代apk文件lib目录中的本地库文件,自动跳过非本地库文件,并更新相关变量。
    */
    UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
    if (it.get() == NULL) {
        return INSTALL_FAILED_INVALID_APK;
    }
    ZipEntryRO entry = NULL;
    int status = NO_NATIVE_LIBRARIES;
    //通过迭代器的next函数,依次迭代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列表的索引小于已经找到的值或者之前还未找到合适的abi类型,则更新返回值
                    status = i;
                }
            }
        }
    }
    //回收内存
    for (int i = 0; i < numAbis; ++i) {
        delete supportedAbis[i];
    }
    return status;
}

可以看到,真正的本地库的abi类型选择是在native层完成的,核心逻辑其实也挺简单:即查看apk文件中lib子目录,看其包含了哪些子目录,每个目录名即一种abi类型;再查看这些abi类型是否在系统支持的abi列表改中,如果在则该abi就是app在系统中将要使用的abi类型,如果同时存在多种类型满足的情况,则选取索引在候选abi列表中较小的那个。

总结一下要点

  • 如果app设置了FLAG_MULTIARCH标志位(即,app要被载入其他app进程), 需要同时选择64位和32位的abi,并根据结果,分别对primaryCpuAbisecondaryCpuAbi赋值;否则,判断一下安装时是否指定了abi类型,如果指定了,就以该值作为abi候选列表,没有指定就用Build.SUPPORTED_ABIS(它是一个String[]类型,每个元素是一种abi类型的名称,如 armeabi,armeabi-V7A,arm64-V8A,X86等),在系统编译时确定的。
  • apk文件是zip文件类型,本地库文件就放在它的lib/子目录下,每一种abi类型的本地库文件存放一个目录,目录名就是该abi类型名。
  • 一个app可能拆分成几个apk文件组成。在为app进行abi类型选择时,依次查看它每一个apk文件,选出最合适abi类型,如果一个app不同apk文件选出的abi类型不同,则为app选取在候选abi列表中索引最小的那个。apk文件的最合适abi类型的标准是:该abi类型在abi候选列表中,且索引最小。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容