Android 提取出Apk的本地库

简述

  Apk安装过程中,会牵涉到本地库文件的提取。它在什么条件下提取,如何提取,我们需要搞清楚。同时在这个过程中,解析包需要用到的主要ABI、次要ABI也会赋值。
  这块相关的代码是在PackageAbiHelperImpl类的derivePackageAbi()方法里,下面就走进代码里面看一下。

PackageAbiHelperImpl derivePackageAbi()

  derivePackageAbi()代码挺长,分段看下,第一段:

    public Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg,
            boolean isUpdatedSystemApp, String cpuAbiOverride, File appLib32InstallDir)
            throws PackageManagerException {
    
    
        // Give ourselves some initial paths; we'll come back for another
        // pass once we've determined ABI below.
        String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(pkg);
        String pkgRawSecondaryCpuAbi = AndroidPackageUtils.getRawSecondaryCpuAbi(pkg);
        final NativeLibraryPaths initialLibraryPaths = deriveNativeLibraryPaths(
                new Abis(pkgRawPrimaryCpuAbi, pkgRawSecondaryCpuAbi),
                appLib32InstallDir, pkg.getPath(),
                pkg.getBaseApkPath(), pkg.isSystem(),
                isUpdatedSystemApp);

        final boolean extractLibs = shouldExtractLibs(pkg, isUpdatedSystemApp);

        final String nativeLibraryRootStr = initialLibraryPaths.nativeLibraryRootDir;
        final boolean useIsaSpecificSubdirs = initialLibraryPaths.nativeLibraryRootRequiresIsa;
        final boolean onIncremental = isIncrementalPath(pkg.getPath());

        String primaryCpuAbi = null;
        String secondaryCpuAbi = null;

        NativeLibraryHelper.Handle handle = null;
        try {
    
    
            handle = AndroidPackageUtils.createNativeLibraryHandle(pkg);
            // TODO(multiArch): This can be null for apps that didn't go through the
            // usual installation process. We can calculate it again, like we
            // do during install time.
            //
            // TODO(multiArch): Why do we need to rescan ASEC apps again ? It seems totally
            // unnecessary.
            final File nativeLibraryRoot = new File(nativeLibraryRootStr);

  首先得到解析包的本来的主要Cpu ABI和次要Cpu ABI。ABI就是应用二进制接口,它是和CPU架构有关的。不同的CPU架构支持的指令集是不同的。
  看一下Android里面ABI和指令架构的对应关系,它的对应关系在VMRuntime类的静态成员变量ABI_TO_INSTRUCTION_SET_MAP中,key为ABI,value为对应的指令集。

    private static final Map<String, String> ABI_TO_INSTRUCTION_SET_MAP
            = new HashMap<String, String>(16);
    static {
    
    
        ABI_TO_INSTRUCTION_SET_MAP.put("armeabi", "arm");
        ABI_TO_INSTRUCTION_SET_MAP.put("armeabi-v7a", "arm");
        ABI_TO_INSTRUCTION_SET_MAP.put("mips", "mips");
        ABI_TO_INSTRUCTION_SET_MAP.put("mips64", "mips64");
        ABI_TO_INSTRUCTION_SET_MAP.put("x86", "x86");
        ABI_TO_INSTRUCTION_SET_MAP.put("x86_64", "x86_64");
        ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a", "arm64");
        ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a-hwasan", "arm64");
    }

  然后调用deriveNativeLibraryPaths()生成一个NativeLibraryPaths对象initialLibraryPaths。看这个类名知道它为本地库的路径名。

deriveNativeLibraryPaths()

  看一下它的方法代码:

    private static NativeLibraryPaths deriveNativeLibraryPaths(final Abis abis,
            final File appLib32InstallDir, final String codePath, final String sourceDir,
            final boolean isSystemApp, final boolean isUpdatedSystemApp) {
    
    
        final File codeFile = new File(codePath);
        final boolean bundledApp = isSystemApp && !isUpdatedSystemApp;

        final String nativeLibraryRootDir;
        final boolean nativeLibraryRootRequiresIsa;
        final String nativeLibraryDir;
        final String secondaryNativeLibraryDir;

        if (isApkFile(codeFile)) {
    
    
            // Monolithic install
            if (bundledApp) {
    
    
                // If "/system/lib64/apkname" exists, assume that is the per-package
                // native library directory to use; otherwise use "/system/lib/apkname".
                final String apkRoot = calculateBundledApkRoot(sourceDir);
                final boolean is64Bit = VMRuntime.is64BitInstructionSet(
                        getPrimaryInstructionSet(abis));

                // This is a bundled system app so choose the path based on the ABI.
                // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this
                // is just the default path.
                final String apkName = deriveCodePathName(codePath);
                final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
                nativeLibraryRootDir = Environment.buildPath(new File(apkRoot), libDir,
                        apkName).getAbsolutePath();

                if (abis.secondary != null) {
    
    
                    final String secondaryLibDir = is64Bit ? LIB_DIR_NAME : LIB64_DIR_NAME;
                    secondaryNativeLibraryDir = Environment.buildPath(new File(apkRoot),
                            secondaryLibDir, apkName).getAbsolutePath();
                } else {
    
    
                    secondaryNativeLibraryDir = null;
                }
            } else {
    
    
                final String apkName = deriveCodePathName(codePath);
                nativeLibraryRootDir = new File(appLib32InstallDir, apkName)
                        .getAbsolutePath();
                secondaryNativeLibraryDir = null;
            }

            nativeLibraryRootRequiresIsa = false;
            nativeLibraryDir = nativeLibraryRootDir;
        } else {
    
    
            // Cluster install
            nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
            nativeLibraryRootRequiresIsa = true;

            nativeLibraryDir = new File(nativeLibraryRootDir,
                    getPrimaryInstructionSet(abis)).getAbsolutePath();

            if (abis.secondary != null) {
    
    
                secondaryNativeLibraryDir = new File(nativeLibraryRootDir,
                        VMRuntime.getInstructionSet(abis.secondary)).getAbsolutePath();
            } else {
    
    
                secondaryNativeLibraryDir = null;
            }
        }
        return new NativeLibraryPaths(nativeLibraryRootDir, nativeLibraryRootRequiresIsa,
                nativeLibraryDir, secondaryNativeLibraryDir);
    }

  可见它的取值和安装Apk时方式有关。一是Monolithic install(整包安装),二是Cluster install(蔟安装)。
  两种方式安装得到解析包的getPath()是结果不同的,Monolithic install安装时,getPath()是Apk文件的路径位置;Cluster install,getPath()是Apk文件的安装目录。
  一、Monolithic install安装
  1、bundledApp为true,代表是系统app,不是处于升级的状态。
  apkroot是根据apk位置得到的根目录。
  VMRuntime.is64BitInstructionSet(getPrimaryInstructionSet(abis))得到支持的指令是不是64位的。先看一下getPrimaryInstructionSet(abis),它是在类InstructionSets中:

    public static String getPrimaryInstructionSet(PackageAbiHelper.Abis abis) {
    
    
        if (abis.primary == null) {
    
    
            return getPreferredInstructionSet();
        }

        return VMRuntime.getInstructionSet(abis.primary);
    }

    private static final String PREFERRED_INSTRUCTION_SET =
            VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);    
    public static String getPreferredInstructionSet() {
    
    
        return PREFERRED_INSTRUCTION_SET;
    }

  可以看到如果abis.primary为null,即没有配置abi。会去取getPreferredInstructionSet(),它则是取的是系统支持的指令。Build.SUPPORTED_ABIS数组为当前系统支持的ABI,取第一个,然后通过VMRuntime.getInstructionSet()方法转化成支持的指令,它的转化就是通过咱们前面介绍的ABI_TO_INSTRUCTION_SET_MAP来进行转化的。
  通过名字还能猜出,Build.SUPPORTED_ABIS数组序列越靠前,越是系统更偏爱的ABI。
  如果abis.primary不为null,直接就通过VMRuntime.getInstructionSet()方法得到支持的指令。
  看一下VMRuntime.is64BitInstructionSet()是怎么实现的

    public static boolean is64BitInstructionSet(String instructionSet) {
    
    
        return "arm64".equals(instructionSet) ||
                "x86_64".equals(instructionSet) ||
                "mips64".equals(instructionSet);
    }

  等于"arm64"、“x86_64”、“mips64"三种中的任意一种,都代表是支持64位指令。
  接着Monolithic install时,取得apkName。apkName在这里是不带后缀”.apk"的。
  然后根据,是不是64位指令,来设置libDir的值。如果是64位,libDir为"lib64",否则为"lib"。
  接着就构建nativeLibraryRootDir。举个例子,如果安装的apk文件路径为"/system/app/exam.apk",则构造完之后nativeLibraryRootDir的值就为:支持64位指令,“/system/lib64/exam”;不支持64位指令,“/system/lib/exam”。
  再接着去判断abis.secondary为null时,secondaryNativeLibraryDir就为null。如果不为null,它的目录和nativeLibraryRootDir对应着,就是nativeLibraryRootDir是lib64,secondaryNativeLibraryDir就为lib;就是nativeLibraryRootDir是lib,secondaryNativeLibraryDir就为lib64。
  2、不是系统包或者是系统包但是处于更新状态
  那如果是Monolithic install,不是系统包或者是系统包但是处于更新状态这种情况呢,(系统app,不是处于升级的状态)的反面。nativeLibraryRootDir为"/data/app-lib" + Apk的文件名。secondaryNativeLibraryDir = null。
  在以上两种情况下,nativeLibraryRootRequiresIsa = false意为在提取库时,不用使用子目录,nativeLibraryDir = nativeLibraryRootDir。将本地库目录设置为和库的根目录相同。

  二、Monolithic install
  上面描述的都是Monolithic install时,如果是Cluster install呢,往下看:
  在这里codeFile是安装的Apk位置的目录。nativeLibraryRootDir为codeFile + “/lib”。nativeLibraryRootRequiresIsa = true。nativeLibraryRootRequiresIsa 是在提取库时,是否使用子目录的意思。
  getPrimaryInstructionSet(abis)上面也讲过了,它的值可能为所有的指令集,在这里可能为arm,arm64,x86_64,x86,mips64,mips其中的一种。这样nativeLibraryDir的值也出来了,为nativeLibraryRootDir + 指令集名。
  同样,abis.secondary为 null,则secondaryNativeLibraryDir为null。否则,secondaryNativeLibraryDir是根据abis.secondary对应的指令集来得到结果的,即nativeLibraryRootDir + 次要指令集名。
  举个例子,Cluster install,APK文件位置为"/data/app/com.example/exam.apk",abis.primary 为"arm64-v8a",那么nativeLibraryRootDir为“/data/app/com.example/lib”,nativeLibraryDir"/data/app/com.example/lib/arm64"。
  最后生成NativeLibraryPaths对象返回。
  可见,deriveNativeLibraryPaths()主要就是得到本地库的主要目录位置、次要目录位置,并将他们封装到NativeLibraryPaths中。
  这样deriveNativeLibraryPaths()方法就说完了。

  返回到derivePackageAbi()中接着看,
  shouldExtractLibs()得到是否需要提取本地代码。看一下:

    private boolean shouldExtractLibs(AndroidPackage pkg, boolean isUpdatedSystemApp) {
    
    
        // We shouldn't extract libs if the package is a library or if extractNativeLibs=false
        boolean extractLibs = !AndroidPackageUtils.isLibrary(pkg) && pkg.isExtractNativeLibs();
        // We shouldn't attempt to extract libs from system app when it was not updated.
        if (pkg.isSystem() && !isUpdatedSystemApp) {
    
    
            extractLibs = false;
        }
        return extractLibs;
    }

  解析包是库时,不能提取。不是库并且解析包的extractNativeLibs=true是才能提取。但是解析包是系统包,并且不是处于更新状态时,也不能提取。
  什么是库呢,就是Manifest文件中配置了"static-library"或"library"标签(和activity标签同一深度)。解析包的extractNativeLibs则是配置Manifest文件中标签"application"属性extractNativeLibs,如果没有配置,默认值为true。
  返回到derivePackageAbi()中接着看,
  通过前面得到的initialLibraryPaths得到本地变量nativeLibraryRootStr、useIsaSpecificSubdirs的值。这里注意一下,前面通过deriveNativeLibraryPaths()得到了主要四个变量值,现在只用了两个变量。nativeLibraryRootStr是本地库根目录名。下面提取本地库文件时,也是使用的它。
  onIncremental代表是否在增量文件系统中。
  调用AndroidPackageUtils.createNativeLibraryHandle(pkg)生成NativeLibraryHelper.Handle对象handle。

    public static NativeLibraryHelper.Handle createNativeLibraryHandle(AndroidPackage pkg)
            throws IOException {
    
    
        return NativeLibraryHelper.Handle.create(
                AndroidPackageUtils.getAllCodePaths(pkg),
                pkg.isMultiArch(),
                pkg.isExtractNativeLibs(),
                pkg.isDebuggable()
        );
    }

  AndroidPackageUtils.getAllCodePaths(pkg)得到应用所有的的Apk位置。大多数应用都是一个Apk,但是split apk方式除了有一个主Apk之外,会有其他的split apk。所以这里会得到所有的apk位置。
  pkg.isMultiArch()的值是来自Manifest文件中标签"application"属性multiArch,如果没有配置,默认值为false。
  pkg.isDebuggable()的值是来自Manifest文件中标签"application"属性debuggable,如果没有配置,默认值为false。
  再看一下Handle的create():

        public static Handle create(List<String> codePaths, boolean multiArch,
                boolean extractNativeLibs, boolean debuggable) throws IOException {
    
    
            final int size = codePaths.size();
            final String[] apkPaths = new String[size];
            final long[] apkHandles = new long[size];
            for (int i = 0; i < size; i++) {
    
    
                final String path = codePaths.get(i);
                apkPaths[i] = path;
                apkHandles[i] = nativeOpenApk(path);
                if (apkHandles[i] == 0) {
    
    
                    // Unwind everything we've opened so far
                    for (int j = 0; j < i; j++) {
    
    
                        nativeClose(apkHandles[j]);
                    }
                    throw new IOException("Unable to open APK: " + path);
                }
            }

            return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable);
        }

  循环codePaths中的Apk文件位置,调用nativeOpenApk(path)得到一个long类型值。然后封装成一个Handle对象。
  nativeOpenApk(path)得到一个long类型值其实是执行C++层中的ZipFileRO对象地址。ZipFileRO对象有一个成员变量ZipArchiveHandle mHandle,它指向打开的apk文件,以后对apk文件进行操作,也是通过mHandle。
  继续回到derivePackageAbi()中,nativeLibraryRoot是由nativeLibraryRootStr生成的文件。

接着看derivePackageAbi()第二段代码:

            // Null out the abis so that they can be recalculated.
            primaryCpuAbi = null;
            secondaryCpuAbi = null;
            if (pkg.isMultiArch()) {
    
    
                int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
                int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
    
    
                    if (extractLibs) {
    
    
                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
                        abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                                useIsaSpecificSubdirs, onIncremental);
                    } else {
    
    
                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                        abi32 = NativeLibraryHelper.findSupportedAbi(
                                handle, Build.SUPPORTED_32_BIT_ABIS);
                    }
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                }

                // Shared library native code should be in the APK zip aligned
                if (abi32 >= 0 && AndroidPackageUtils.isLibrary(pkg) && extractLibs) {
    
    
                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                            "Shared library native lib extraction not supported");
                }

                maybeThrowExceptionForMultiArchCopy(
                        "Error unpackaging 32 bit native libs for multiarch app.", abi32);

                if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
    
    
                    if (extractLibs) {
    
    
                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
                        abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
                                useIsaSpecificSubdirs, onIncremental);
                    } else {
    
    
                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                        abi64 = NativeLibraryHelper.findSupportedAbi(
                                handle, Build.SUPPORTED_64_BIT_ABIS);
                    }
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                }

                maybeThrowExceptionForMultiArchCopy(
                        "Error unpackaging 64 bit native libs for multiarch app.", abi64);

                if (abi64 >= 0) {
    
    
                    // Shared library native libs should be in the APK zip aligned
                    if (extractLibs && AndroidPackageUtils.isLibrary(pkg)) {
    
    
                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                                "Shared library native lib extraction not supported");
                    }
                    primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
                }

                if (abi32 >= 0) {
    
    
                    final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                    if (abi64 >= 0) {
    
    
                        if (pkg.isUse32BitAbi()) {
    
    
                            secondaryCpuAbi = primaryCpuAbi;
                            primaryCpuAbi = abi;
                        } else {
    
    
                            secondaryCpuAbi = abi;
                        }
                    } else {
    
    
                        primaryCpuAbi = abi;
                    }
                }

  解析包有的支持多架构,有的不支持。下面就要分解析包是支持,还是不支持多架构来处理。这第二段代码是处理多架构的。

解析包支持多架构

  Build.SUPPORTED_32_BIT_ABIS.length > 0,意味着系统支持32位ABI。在解析包可以提取库的情况下,调用NativeLibraryHelper.copyNativeBinariesForSupportedAbi(),如果不能提取则调用NativeLibraryHelper.findSupportedAbi()。
  返回来的abi32是啥呢,它是Build.SUPPORTED_32_BIT_ABIS数组的序列。它是数组中的ABI与Apk文件中的".so"文件的ABI相符合的条件下序列。并且,Build.SUPPORTED_32_BIT_ABIS数组的序列的下标越小,优先级越高。
  同时Build.SUPPORTED_64_BIT_ABIS.length > 0,的情况下,和32的逻辑是一样的。所以,我们搞明白一个,另一个也就明白了。
  NativeLibraryHelper.copyNativeBinariesForSupportedAbi()做了什么呢?在这里,它把符合ABI数组序列的本地库文件都给提取出来了。具体的看下面分析。
  如果abi64>0,则将primaryCpuAbi设置为系统支持,Apk也存在的ABI
  pkg.isUse32BitAbi()也是来自解析包的配置Manifest文件中标签"application"属性use32bitAbi,如果没有配置,默认值为false。
  所以解析包配置了pkg.isUse32BitAbi(),那它就得将主要ABI primaryCpuAbi设置为32位的ABI,次要ABI secondaryCpuAbi设置为它原来的主要ABI。
  如果只找到32位的ABI,这个时候,就将主要ABI primaryCpuAbi设置为32位的ABI,次要ABI secondaryCpuAbi不设置。
  下面就看看NativeLibraryHelper.copyNativeBinariesForSupportedAbi()的代码,它是如何将本地库文件提取出来的。

将二进制代码库提取出来

    public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
            String[] abiList, boolean useIsaSubdir, boolean isIncremental) throws IOException {
    
    
        /*
         * If this is an internal application or our nativeLibraryPath points to
         * the app-lib directory, unpack the libraries if necessary.
         */
        int abi = findSupportedAbi(handle, abiList);
        if (abi < 0) {
    
    
            return abi;
        }

        /*
         * If we have a matching instruction set, construct a subdir under the native
         * library root that corresponds to this instruction set.
         */
        final String supportedAbi = abiList[abi];
        final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
        final File subDir;
        if (useIsaSubdir) {
    
    
            subDir = new File(libraryRoot, instructionSet);
        } else {
    
    
            subDir = libraryRoot;
        }

        if (isIncremental) {
    
    
            int res =
                    incrementalConfigureNativeBinariesForSupportedAbi(handle, subDir, supportedAbi);
            if (res != PackageManager.INSTALL_SUCCEEDED) {
    
    
                // TODO(b/133435829): the caller of this function expects that we return the index
                // to the supported ABI. However, any non-negative integer can be a valid index.
                // We should fix this function and make sure it doesn't accidentally return an error
                // code that can also be a valid index.
                return res;
            }
            return abi;
        }

        // For non-incremental, use regular extraction and copy
        createNativeLibrarySubdir(libraryRoot);
        if (subDir != libraryRoot) {
    
    
            createNativeLibrarySubdir(subDir);
        }

        // Even if extractNativeLibs is false, we still need to check if the native libs in the APK
        // are valid. This is done in the native code.
        int copyRet = copyNativeBinaries(handle, subDir, supportedAbi);
        if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
    
    
            return copyRet;
        }

        return abi;
    }

  首先调用findSupportedAbi(),得到apk文件中,适合ABI数组abiList的序列,看一下它的代码:

findSupportedAbi()

    public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
    
    
        int finalRes = NO_NATIVE_LIBRARIES;
        for (long apkHandle : handle.apkHandles) {
    
    
            final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable);
            if (res == NO_NATIVE_LIBRARIES) {
    
    
                // No native code, keep looking through all APKs.
            } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
    
    
                // Found some native code, but no ABI match; update our final
                // result if we haven't found other valid code.
                if (finalRes < 0) {
    
    
                    finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
                }
            } else if (res >= 0) {
    
    
                // Found valid native code, track the best ABI match
                if (finalRes < 0 || res < finalRes) {
    
    
                    finalRes = res;
                }
            } else {
    
    
                // Unexpected error; bail
                return res;
            }
        }
        return finalRes;
    }

  在handle的所有文件中,执行nativeFindSupportedAbi()方法,其中参数apkHandle对应着C++层的ZipFileRO对象,supportedAbis是系统支持的ABI,handle.debuggable是否可以调试。
  nativeFindSupportedAbi()实现在platform\frameworks\base\core\jni\com_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, jboolean debuggable)
{
    
    
    return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable);
}

static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray,
        jboolean debuggable) {
    
    
    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)));
    }

    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
    
    
        return INSTALL_FAILED_INVALID_APK;
    }

    std::unique_ptr<NativeLibrariesIterator> it(
            NativeLibrariesIterator::create(zipFile, debuggable));
    if (it.get() == NULL) {
    
    
        return INSTALL_FAILED_INVALID_APK;
    }

    ZipEntryRO entry = NULL;
    int status = NO_NATIVE_LIBRARIES;
    while ((entry = it->next()) != NULL) {
    
    
        // We're currently in the lib/ directory of the APK, so it does have some native
        // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
        // libraries match.
        if (status == NO_NATIVE_LIBRARIES) {
    
    
            status = INSTALL_FAILED_NO_MATCHING_ABIS;
        }

        const char* fileName = it->currentEntry();
        const char* lastSlash = it->lastSlash();

        // Check to see if this CPU ABI matches what we are looking for.
        const char* abiOffset = fileName + APK_LIB_LEN;
        const size_t abiSize = lastSlash - abiOffset;
        for (int i = 0; i < numAbis; i++) {
    
    
            const ScopedUtfChars* abi = supportedAbis[i];
            if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
    
    
                // The entry that comes in first (i.e. with a lower index) has the higher priority.
                if (((i < status) && (status >= 0)) || (status < 0) ) {
    
    
                    status = i;
                }
            }
        }
    }

    for (int i = 0; i < numAbis; ++i) {
    
    
        delete supportedAbis[i];
    }

    return status;
}

  C++实现的findSupportedAbi()其实主要就是拿系统支持的ABI来和Apk文件中lib文件夹下面的包名来进行比较,得到这个Apk支持的系统ABI的序列。并且取得序列以最小的作为结果。
  看一下C++实现的findSupportedAbi(),通过Java层传递进去的ABI数组,放到supportedAbis变量中,将Java层传递进去的apkHandle,转化为ZipFileRO对象指针zipFile。然后通过zipFile,调用NativeLibrariesIterator::create(zipFile, debuggable))生成一个NativeLibrariesIterator对象指针。
  我们知道Apk文件是一个Zip文件,它里面由好多个文件。而在这块,它用ZipFileRO来表示压缩文件,而里面的单个文件是用ZipEntryRO来表示。NativeLibrariesIterator::create(zipFile, debuggable))主要是通过ZipFileRO的startIteration()找到第一个以"lib/“开头的_ZipEntryRO。后面就通过循环NativeLibrariesIterator对象的next()方法,来找以"lib/“开头,”.so"结尾的单个压缩项。
  其中lastSlash是文件名最后一个”/"的位置,这样就能截取出来lib文件夹后面的Abi文件名,再循环和supportedAbis数组中一个进行比对,如果相等就是找到了。然后将找到supportedAbis数组的序列号记录下来,如果找到多个,那么会选取最小的序列。
  这就是C++实现的findSupportedAbi()的逻辑,不明白可以对着代码再看看。
  我们再回到Java层的findSupportedAbi()方法。如果存在多个Apk(split apks),也是循环,找到supportedAbis数组序列最小的那个。
  这样我们就能回到NativeLibraryHelper.copyNativeBinariesForSupportedAbi()里了。
  接着往下,findSupportedAbi()返回的abi小于0,则认为没找到,就直接返回了。
  abi正确时,取到合适的ABI supportedAbi,然后再得到指令集instructionSet。
  如果使用子目录,会在根目录下加上指令集名称的目录。
  如果是在增量文件系统上,则需要调用增量文件系统服务进行处理相应的文件创建,数据写入。然后返回结果。
  如果不是在增量文件系统上,则创建目录libraryRoot。如果subDir和libraryRoot不同,也会创建subDir。
  然后调用copyNativeBinaries()方法,将库复制到目录subDir中。

复制库文件到目录中

  它的实现在copyNativeBinaries()方法中,看一下:

    public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
    
    
        for (long apkHandle : handle.apkHandles) {
    
    
            int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
                    handle.extractNativeLibs, handle.debuggable);
            if (res != INSTALL_SUCCEEDED) {
    
    
                return res;
            }
        }
        return INSTALL_SUCCEEDED;
    }

  调用了一个循环将所有的Apk文件,通过nativeCopyNativeBinaries(),将根据handle.extractNativeLibs参数,是否赋值库文件到sharedLibraryDir目录中。
  看一下nativeCopyNativeBinaries(),它实现在platform\frameworks\base\core\jni\com_android_internal_content_NativeLibraryHelper.cpp文件中,对应的方法是com_android_internal_content_NativeLibraryHelper_copyNativeBinaries()方法:

static jint
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
        jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
        jboolean extractNativeLibs, jboolean debuggable)
{
    
    
    void* args[] = {
    
     &javaNativeLibPath, &extractNativeLibs };
    return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
            copyFileIfChanged, reinterpret_cast<void*>(args));
}

  可见它调用了iterateOverNativeFiles()方法:

iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
                       jboolean debuggable, iterFunc callFunc, void* callArg) {
    
    
    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == NULL) {
    
    
        return INSTALL_FAILED_INVALID_APK;
    }

    std::unique_ptr<NativeLibrariesIterator> it(
            NativeLibrariesIterator::create(zipFile, debuggable));
    if (it.get() == NULL) {
    
    
        return INSTALL_FAILED_INVALID_APK;
    }

    const ScopedUtfChars cpuAbi(env, javaCpuAbi);
    if (cpuAbi.c_str() == NULL) {
    
    
        // This would've thrown, so this return code isn't observable by
        // Java.
        return INSTALL_FAILED_INVALID_APK;
    }
    ZipEntryRO entry = NULL;
    while ((entry = it->next()) != NULL) {
    
    
        const char* fileName = it->currentEntry();
        const char* lastSlash = it->lastSlash();

        // Check to make sure the CPU ABI of this file is one we support.
        const char* cpuAbiOffset = fileName + APK_LIB_LEN;
        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;

        if (cpuAbi.size() == cpuAbiRegionSize && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
    
    
            install_status_t ret = callFunc(env, callArg, zipFile, entry, lastSlash + 1);

            if (ret != INSTALL_SUCCEEDED) {
    
    
                ALOGV("Failure for entry %s", lastSlash + 1);
                return ret;
            }
        }
    }

    return INSTALL_SUCCEEDED;
}

  iterateOverNativeFiles()方法里,还是先通过apkHandle找到ZipFileRO对象。接着又创建成NativeLibrariesIterator对象指针,这个前面有讲。下面就是遍历所有的"lib/“开头,”.so"结尾的文件,然后和参数ABI javaCpuAbi相比较,如果相等就调用函数callFunc。在这里,它是copyFileIfChanged(),看一下它:

static install_status_t
copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
{
    
    
	……
    if (!extractNativeLibs) {
    
    
        // check if library is uncompressed and page-aligned
        if (method != ZipFileRO::kCompressStored) {
    
    
            ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
                fileName);
            return INSTALL_FAILED_INVALID_APK;
        }
		……
        return INSTALL_SUCCEEDED;
    }
    // Build local file path
    const size_t fileNameLen = strlen(fileName);
    char localFileName[nativeLibPath.size() + fileNameLen + 2];
    ……
    // Only copy out the native file if it's different.
    struct tm t;
    ZipUtils::zipTimeToTimespec(when, &t);
    const time_t modTime = mktime(&t);
    struct stat64 st;
    if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) {
    
    
        return INSTALL_SUCCEEDED;
    }
    ……
    int fd = mkstemp(localTmpFileName);	
    ……
    if (!zipFile->uncompressEntry(zipEntry, fd)) {
    
    
        ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
        close(fd);
        unlink(localTmpFileName);
        return INSTALL_FAILED_CONTAINER_ERROR;
    }
    ……
    // Finally, rename it to the final name.
    if (rename(localTmpFileName, localFileName) < 0) {
    
    
        ALOGE("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno));
        unlink(localTmpFileName);
        return INSTALL_FAILED_CONTAINER_ERROR;
    } 
    return INSTALL_SUCCEEDED;
}       

  copyFileIfChanged()是实际执行将库拷贝出来的地方。代码也挺长,就挑重要的代码来说了。
  extractNativeLibs代表是否提取库,如果不提取,它会去检查库是否被压缩了,如果压缩会报INSTALL_FAILED_INVALID_APK错误码。
  localFileName是提取出来的库的文件名。还会将localFileName和Apk中的文件用isFileDifferent(localFileName, uncompLen, modTime, crc, &st)进行比较,如果一样,说明不用执行复制,已经存在。但是如果不同,需要复制。
  我们看到提取出来的时候,是先将它提取到一个临时文件fd 中。然后提取的方法是zipFile->uncompressEntry(zipEntry, fd),最后一步就是把提取出来的临时文件,改成目标的文件名,这样就把库提取出来了。
  这样,终于把copyNativeBinariesForSupportedAbi()说完了。
  现在要回到derivePackageAbi()中,继续后续的流程。

  derivePackageAbi()第三段代码:

            } else {
    
    
                String[] abiList = (cpuAbiOverride != null)
                        ? new String[]{
    
    cpuAbiOverride} : Build.SUPPORTED_ABIS;

                // If an app that contains RenderScript has target API level < 21, it needs to run
                // with 32-bit ABI, and its APK file will contain a ".bc" file.
                // If an app that contains RenderScript has target API level >= 21, it can run with
                // either 32-bit or 64-bit ABI, and its APK file will not contain a ".bc" file.
                // Therefore, on a device that supports both 32-bit and 64-bit ABIs, we scan the app
                // APK to see if it has a ".bc" file. If so, we will run it with 32-bit ABI.
                // However, if the device only supports 64-bit ABI but does not support 32-bit ABI,
                // we will fail the installation for such an app because it won't be able to run.
                boolean needsRenderScriptOverride = false;
                // No need to check if the device only supports 32-bit
                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null
                        && NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
    
    
                    if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
    
    
                        abiList = Build.SUPPORTED_32_BIT_ABIS;
                        needsRenderScriptOverride = true;
                    } else {
    
    
                        throw new PackageManagerException(
                                INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
                                "Apps that contain RenderScript with target API level < 21 are not "
                                        + "supported on 64-bit only platforms");
                    }
                }

                final int copyRet;
                if (extractLibs) {
    
    
                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
                    copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                            nativeLibraryRoot, abiList, useIsaSpecificSubdirs, onIncremental);
                } else {
    
    
                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                    copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
                }
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

                if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
    
    
                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                            "Error unpackaging native libs for app, errorCode=" + copyRet);
                }

                if (copyRet >= 0) {
    
    
                    // Shared libraries that have native libs must be multi-architecture
                    if (AndroidPackageUtils.isLibrary(pkg)) {
    
    
                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                                "Shared library with native libs must be multiarch");
                    }
                    primaryCpuAbi = abiList[copyRet];
                } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES
                        && cpuAbiOverride != null) {
    
    
                    primaryCpuAbi = cpuAbiOverride;
                } else if (needsRenderScriptOverride) {
    
    
                    primaryCpuAbi = abiList[0];
                }
            }
        } catch (IOException ioe) {
    
    
            Slog.e(PackageManagerService.TAG, "Unable to get canonical file " + ioe.toString());
        } finally {
    
    
            IoUtils.closeQuietly(handle);
        }

  这第三段代码是处理解析包不支持多架构的情况。

解析包不支持多架构

  看一下derivePackageAbi()不支持多架构的情况。
  参数cpuAbiOverride不为null,就将它设置到abiList数组中;参数cpuAbiOverride为null,就将abiList设置为Build.SUPPORTED_ABIS,系统支持的ABI数组。
  接下来,就是处理RenderScript的情况。解析包Apk如果带".bc"文件,它支持RenderScript,但是它只能运行在32位ABI上。所以在这种情况,abiList 只能选 Build.SUPPORTED_32_BIT_ABIS。如果Build.SUPPORTED_32_BIT_ABIS的长度小于等于0,会报PackageManagerException。
  接着如果需要提取本地库文件,也是走NativeLibraryHelper.copyNativeBinariesForSupportedAbi()方法,见上面。如果不提取,直接走NativeLibraryHelper.findSupportedAbi(handle, abiList),见上面
  如果得到结果copyRet >= 0,则将primaryCpuAbi = abiList[copyRet]。
  除了上一种情况之外,如果copyRet == PackageManager.NO_NATIVE_LIBRARIES,则是Apk文件中不存在lib包。这个时候,如果设置了cpuAbiOverride,就将primaryCpuAbi = cpuAbiOverride。
  除了上两种情况之外,如果app包含RenderScript。就将primaryCpuAbi = abiList[0]。
  我们发现,不支持多架构时,是只设置primaryCpuAbi ,不设置次要ABI secondaryCpuAbi。
  不支持多架构代码完毕。

  这时,会走derivePackageAbi()最后一段代码,

        final Abis abis = new Abis(primaryCpuAbi, secondaryCpuAbi);
        return new Pair<>(abis,
                deriveNativeLibraryPaths(abis, appLib32InstallDir,
                        pkg.getPath(), pkg.getBaseApkPath(), pkg.isSystem(),
                        isUpdatedSystemApp));

  主要就是调用deriveNativeLibraryPaths()封装成NativeLibraryPaths对象。
  这里调用和刚开始调用有什么不同吗?主要就是abis里面的主要、次要CPU ABI不同。
  在刚开始解析包里面主要、次要CPU ABI都是空的,现在经过前面一通查找,值都已经确定下来了。现在再确定一下主要库目录和次要库目录。
最后,封装成Pair对象返回。

总结

  一、现在我们知道了在什么情况下,会提取本地库文件?
  解析包不是库并且解析包的extractNativeLibs=true。
  在上面这种条件下,还要排除掉一种情况:解析包是系统包,并且不是处于更新状态时,也不能提取。
  二、如何提取本地库文件?
  主要就是调用copyNativeBinariesForSupportedAbi()来实现的。大致意思就是从压缩包里找到"lib/"开头的文件,然后通过和系统支持的ABI进行比较,找到最合适的ABI,然后确定提取路径,最后再通过合适的ABI,去APK文件中找到对应的包,将之提取出来。

猜你喜欢

转载自blog.csdn.net/q1165328963/article/details/132391414
今日推荐