美文网首页Android开发Android知识Android技术知识
Android6.0,本地库(Native Library)的选

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

作者: 巫屋 | 来源:发表于2017-12-31 18:55 被阅读0次

什么是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候选列表中,且索引最小。

相关文章

网友评论

    本文标题:Android6.0,本地库(Native Library)的选

    本文链接:https://www.haomeiwen.com/subject/nduagxtx.html