Skip to content

native-libs 扫描

追踪 Android 17 的 ABI 推导、native library 路径、extractNativeLibs、multiArch 与清理边界。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描native libraryABINativeLibraryHelper源码阅读

native-libs 扫描 ​

本文面向已经读过 扫描签名校验 和 扫描并行化 的读者,继续追踪签名通过后的 native library 状态。前文解释 parser 和签名详情如何进入扫描;本文解释 primaryCpuAbi、secondaryCpuAbi、nativeLibraryRootDir、nativeLibraryDir 从哪里来,以及这些字段如何影响复制、加载和卸载清理。

Android 17 的 native-libs 处理同时存在三种不同场景:预装 system/vendor/product 等分区中的 bundled app、/data/app 中的用户安装/更新包、以及 multiArch、APEX、incremental 和 16 KB 对齐等条件路径。PackageAbiHelperImpl 负责策略和路径,NativeLibraryHelper 负责 APK 内 native code 的探测/复制,ScanPackageUtils 负责把结果写回 ParsedPackage 和 PackageSetting。

读完后,读者应能从 ScanPackageUtils.scanPackageOnly() 找到 ABI 推导入口,解释 monolithic 与 cluster 安装的路径差异;能判断 extractNativeLibs=false 为什么仍然要检查 .so;能区分 NO_NATIVE_LIBRARIES、INSTALL_FAILED_NO_MATCHING_ABIS 和 multiArch 全匹配失败;还能从 legacyNativeLibraryPath 追到卸载时的清理消费者。

1. 处理边界 ​

1.1 三个 owner ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelper.java
  • frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
  • frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java
对象owner负责内容结果消费者
ParsedPackageparser/扫描流程Manifest 中的 multiArch、extractNativeLibs、ABI 原始字段ABI 推导、ApplicationInfo
PackageAbiHelperImplPMS 注入依赖设备 ABI 选择、路径推导、shared user 调整ScanPackageUtils
NativeLibraryHelper.Handlenative helper打开 base/split APK、保存 native handle 和包属性findSupportedAbi()、复制、对齐检查
PackageSettingSettings持久化 ABI、native library root、用户/更新状态查询、升级、卸载清理

ABI 字符串和库目录不是同一个值:primaryCpuAbi 例如 arm64-v8a,nativeLibraryDir 则是最终加载目录;nativeLibraryRootDir 可能是 cluster 包的 lib 根目录,nativeLibraryRootRequiresIsa 决定是否还要追加 instruction set 子目录。

1.2 扫描时机 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java

java
final File appLib32InstallDir = getAppLib32InstallDir();
// The native libs of Apex is located in apex_payload.img, don't need to parse it from
// the original apex file
if (!isApex) {
    if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
        if (needToDeriveAbi) {
            final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
                    packageAbiHelper.derivePackageAbi(parsedPackage, isSystemApp,
                            isUpdatedSystemApp, cpuAbiOverride, appLib32InstallDir);
            derivedAbi.first.applyTo(parsedPackage);
            derivedAbi.second.applyTo(parsedPackage);
        } else {
            parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
                    .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
                    isUpdatedSystemApp, appLib32InstallDir).applyTo(parsedPackage);
        }
    }
}

这段代码位于 scanPackageOnly(),但省略了 ABI 细节分支。它揭示了三个时机:

  • first boot/upgrade、stub 或没有旧设置时重新 derive ABI;
  • 普通重扫描复用 PackageSetting 中的 ABI,再根据最终 code path 重算目录;
  • SCAN_AS_APEX 时跳过普通 APK native library 推导,因为 APEX 的 native code 在 payload image 中。

扫描得到的 ABI 会在同一方法后段写入 PackageSetting,随后由 ApplicationInfo 派生给 zygote/类加载器使用。它不是 parser 线程中的独立后台任务。

2. 路径模型 ​

2.1 接口数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelper.java

java
final class NativeLibraryPaths {
    public final String nativeLibraryRootDir;
    public final boolean nativeLibraryRootRequiresIsa;
    public final String nativeLibraryDir;
    public final String secondaryNativeLibraryDir;

    @VisibleForTesting
    NativeLibraryPaths(String nativeLibraryRootDir,
            boolean nativeLibraryRootRequiresIsa, String nativeLibraryDir,
            String secondaryNativeLibraryDir) {
        this.nativeLibraryRootDir = nativeLibraryRootDir;
        this.nativeLibraryRootRequiresIsa = nativeLibraryRootRequiresIsa;
        this.nativeLibraryDir = nativeLibraryDir;
        this.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
    }

    public void applyTo(ParsedPackage pkg) {
        pkg.setNativeLibraryRootDir(nativeLibraryRootDir)
                .setNativeLibraryRootRequiresIsa(nativeLibraryRootRequiresIsa)
                .setNativeLibraryDir(nativeLibraryDir)
                .setSecondaryNativeLibraryDir(secondaryNativeLibraryDir);
    }
}

final class Abis {
    public final String primary;
    public final String secondary;

    public void applyTo(ParsedPackage pkg) {
        pkg.setPrimaryCpuAbi(primary)
                .setSecondaryCpuAbi(secondary);
    }

    public void applyTo(PackageSetting pkgSetting) {
        // pkgSetting might be null during rescan following uninstall of updates
        if (pkgSetting != null) {
            pkgSetting.setPrimaryCpuAbi(primary);
            pkgSetting.setSecondaryCpuAbi(secondary);
        }
    }
}

NativeLibraryPaths 和 Abis 都是策略结果对象。applyTo(ParsedPackage) 只写入当前解析对象;Abis.applyTo(PackageSetting) 允许设置为空,因为卸载 system app 更新后重新扫描 bundled app 时,正式设置可能稍后才从解析包建立。路径对象没有直接负责创建目录或复制 .so。

2.2 bundled 根目录 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
private static String calculateBundledApkRoot(final String codePathString) {
    final File codePath = new File(codePathString);
    final File codeRoot;
    if (FileUtils.contains(Environment.getRootDirectory(), codePath)) {
        codeRoot = Environment.getRootDirectory();
    } else if (FileUtils.contains(Environment.getOemDirectory(), codePath)) {
        codeRoot = Environment.getOemDirectory();
    } else if (FileUtils.contains(Environment.getVendorDirectory(), codePath)) {
        codeRoot = Environment.getVendorDirectory();
    } else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) {
        codeRoot = Environment.getOdmDirectory();
    } else if (FileUtils.contains(Environment.getProductDirectory(), codePath)) {
        codeRoot = Environment.getProductDirectory();
    } else if (FileUtils.contains(Environment.getSystemExtDirectory(), codePath)) {
        codeRoot = Environment.getSystemExtDirectory();
    } else if (FileUtils.contains(Environment.getApexDirectory(), codePath)) {
        String fullPath = codePath.getAbsolutePath();
        String[] parts = fullPath.split(File.separator);
        if (parts.length > 2) {
            codeRoot = new File(parts[1] + File.separator + parts[2]);
        } else {
            Slog.w(PackageManagerService.TAG, "Can't canonicalize code path " + codePath);
            codeRoot = Environment.getApexDirectory();
        }
    } else {
        // Unrecognized code path; use its top real segment as the apk root.
        try {
            File f = codePath.getCanonicalFile();
            File parent = f.getParentFile();
            File tmp;
            while ((tmp = parent.getParentFile()) != null) {
                f = parent;
                parent = tmp;
            }
            codeRoot = f;
            Slog.w(PackageManagerService.TAG, "Unrecognized code path "
                    + codePath + " - using " + codeRoot);
        } catch (IOException e) {
            Slog.w(PackageManagerService.TAG, "Can't canonicalize code path " + codePath);
            return Environment.getRootDirectory().getPath();
        }
    }
    return codeRoot.getPath();
}

bundled app 的根目录不是由字符串前缀手工猜测,而是用 FileUtils.contains() 依次匹配 system、oem、vendor、odm、product、system_ext 和 APEX。未识别路径才退回 canonical path 的顶层 segment,并记录 warning。这个 fallback 能避免方法直接失败,但不代表未知路径具有正确的系统分区语义。

2.3 安装形态 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
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) {
            final String apkRoot = calculateBundledApkRoot(sourceDir);
            final boolean is64Bit = VMRuntime.is64BitInstructionSet(
                    getPrimaryInstructionSet(abis));
            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();
        secondaryNativeLibraryDir = abis.secondary == null ? null
                : new File(nativeLibraryRootDir,
                        VMRuntime.getInstructionSet(abis.secondary)).getAbsolutePath();
    }
    return new NativeLibraryPaths(nativeLibraryRootDir, nativeLibraryRootRequiresIsa,
            nativeLibraryDir, secondaryNativeLibraryDir);
}

路径规则可以直接从代码得到:

  • bundled monolithic APK:根目录取所属分区,再选择 lib 或 lib64 和 APK 名称;双 ABI 时 secondary 使用另一侧目录。
  • 非 bundled monolithic APK:根目录位于 appLib32InstallDir/<apkName>,不追加 ISA 子目录。
  • cluster 包:根目录是 <codePath>/lib,nativeLibraryDir 再追加 primary instruction set,secondary 追加另一 instruction set。

因此不能用“所有 APK 都在 <package>/lib/<isa>”解释 system 预装包;也不能看到 nativeLibraryRootRequiresIsa=false 就认为没有 ABI 目录,因为 bundled monolithic 的 lib64/<apkName> 已经把架构编码在根路径中。

3. ABI 选择 ​

3.1 bundled app ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
private Abis getBundledAppAbi(AndroidPackage pkg, String apkRoot, String apkName) {
    final File codeFile = new File(pkg.getPath());
    final boolean has64BitLibs;
    final boolean has32BitLibs;

    if (isApkFile(codeFile)) {
        has64BitLibs = new File(apkRoot,
                new File(LIB64_DIR_NAME, apkName).getPath()).exists();
        has32BitLibs = new File(apkRoot,
                new File(LIB_DIR_NAME, apkName).getPath()).exists();
    } else {
        final File rootDir = new File(codeFile, LIB_DIR_NAME);
        if (!ArrayUtils.isEmpty(Build.SUPPORTED_64_BIT_ABIS)
                && !TextUtils.isEmpty(Build.SUPPORTED_64_BIT_ABIS[0])) {
            final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_64_BIT_ABIS[0]);
            has64BitLibs = new File(rootDir, isa).exists();
        } else {
            has64BitLibs = false;
        }
        if (!ArrayUtils.isEmpty(Build.SUPPORTED_32_BIT_ABIS)
                && !TextUtils.isEmpty(Build.SUPPORTED_32_BIT_ABIS[0])) {
            final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_32_BIT_ABIS[0]);
            has32BitLibs = new File(rootDir, isa).exists();
        } else {
            has32BitLibs = false;
        }
    }

    if (has64BitLibs && !has32BitLibs) {
        return new Abis(Build.SUPPORTED_64_BIT_ABIS[0], null);
    } else if (has32BitLibs && !has64BitLibs) {
        return new Abis(Build.SUPPORTED_32_BIT_ABIS[0], null);
    } else if (has32BitLibs && has64BitLibs) {
        if (!pkg.isMultiArch()) {
            Slog.e(PackageManagerService.TAG,
                    "Package " + pkg + " has multiple bundled libs, but is not multiarch.");
        }
        if (VMRuntime.is64BitInstructionSet(getPreferredInstructionSet())) {
            return new Abis(Build.SUPPORTED_64_BIT_ABIS[0], Build.SUPPORTED_32_BIT_ABIS[0]);
        } else {
            return new Abis(Build.SUPPORTED_32_BIT_ABIS[0], Build.SUPPORTED_64_BIT_ABIS[0]);
        }
    }
    return new Abis(null, null);
}

bundled app 不打开 APK 再扫描 .so,而是观察分区中已经部署的 per-package library 目录。只有 64 位目录时选 64 位,只有 32 位目录时选 32 位;两者都有时记录双 ABI,并在非 multiArch 包上打印 error,但仍按设备偏好的 instruction set 选择 primary。

这段逻辑假定系统镜像构建合理,接口注释明确说“不验证这些信息”。如果目录布局与 Manifest 不一致,得到的 ABI 可能仍被接受;问题会在后续加载或对齐验证中暴露。

3.2 非 multiArch ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

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

boolean needsRenderScriptOverride = false;
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) {
    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];
}

非 multiArch 包只选择一个 abiList 索引。cpuAbiOverride 会把候选列表缩成一个 ABI;没有 native library 时仍会把 override 记录为 primary ABI。RenderScript bitcode 在 64 位设备上强制走 32 位 ABI,若设备没有 32 位 ABI 则安装失败。

extractLibs=false 只改变调用 copyNativeBinariesForSupportedAbi() 还是 findSupportedAbi(),不代表跳过检查。即使不解压,findSupportedAbi() 仍要判断 APK 中是否存在设备可加载的 native code。

3.3 multiArch ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
if (pkg.isMultiArch()) {
    // Force the match for target SDK >= VANILLA_ICE_CREAM when no override is set.
    final boolean forceMatch =
            pkg.getTargetSdkVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM
                    && cpuAbiOverride == null;

    String[] supported32BitAbis = forceMatch ? getNativelySupported32BitAbis()
            : Build.SUPPORTED_32_BIT_ABIS;
    String[] supported64BitAbis = forceMatch ? getNativelySupported64BitAbis()
            : Build.SUPPORTED_64_BIT_ABIS;

    int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
    int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
    if (supported32BitAbis.length > 0) {
        abi32 = extractLibs
                ? NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                        nativeLibraryRoot, supported32BitAbis,
                        useIsaSpecificSubdirs, onIncremental)
                : NativeLibraryHelper.findSupportedAbi(handle, supported32BitAbis);
    }
    maybeThrowExceptionForMultiArchCopy(
            "Error unpackaging 32 bit native libs for multiarch app.", abi32,
            forceMatch && supported32BitAbis.length > 0);

    if (supported64BitAbis.length > 0) {
        abi64 = extractLibs
                ? NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                        nativeLibraryRoot, supported64BitAbis,
                        useIsaSpecificSubdirs, onIncremental)
                : NativeLibraryHelper.findSupportedAbi(handle, supported64BitAbis);
    }
    maybeThrowExceptionForMultiArchCopy(
            "Error unpackaging 64 bit native libs for multiarch app.", abi64,
            forceMatch && supported64BitAbis.length > 0);
}

multiArch 会分别对 32 位和 64 位候选 ABI 调用 helper。Android 17 对 target SDK >= VANILLA_ICE_CREAM 且没有 ABI override 的包使用“原生支持 ABI”列表,排除由 native bridge 模拟的 ABI;否则使用 Build.SUPPORTED_*_ABIS。

maybeThrowExceptionForMultiArchCopy() 对 NO_NATIVE_LIBRARIES 和普通 NO_MATCHING_ABIS 保持宽容,但在 forceMatch 下把没有覆盖全部原生 ABI 的结果升级为 INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS。因此“multiArch 包没有 32 位库”不一定失败,是否失败取决于 target SDK、设备原生 ABI 和 helper 返回值。

3.4 ABI 顺序 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
if (abi64 >= 0) {
    primaryCpuAbi = supported64BitAbis[abi64];
}

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

如果 64 位和 32 位都匹配,默认 64 位为 primary;Manifest 的 is32BitAbiPreferred 才会交换 primary/secondary。只有一侧匹配时,该侧成为 primary。这个排序会影响路径生成和进程 instruction set,不只是一个展示字段。

4. Native helper ​

4.1 Handle 生命周期 ​

源码文件:frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

java
public static Handle create(List<String> codePaths, boolean multiArch,
        boolean extractNativeLibs, boolean debuggable, boolean isPageSizeCompatDisabled)
        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,
            isPageSizeCompatDisabled);
}

@Override
public void close() {
    for (long apkHandle : apkHandles) {
        nativeClose(apkHandle);
    }
    mGuard.close();
    mClosed = true;
}

Handle 为 base/split APK 各保存一个 native handle,同时携带 multiArch、extractNativeLibs、debuggable 和 page-size compatibility 属性。打开第 N 个 APK 失败时,会先关闭前面已经打开的 handle;正常路径由 PackageAbiHelperImpl 的 finally { IoUtils.closeQuietly(handle); } 关闭。native handle 泄漏会影响后续扫描和文件访问,因此 close 不是可选的收尾。

4.2 ABI 探测 ​

源码文件:frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

java
public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
    int finalRes = NO_NATIVE_LIBRARIES;
    for (long apkHandle : handle.apkHandles) {
        final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
        if (res == NO_NATIVE_LIBRARIES) {
            // No native code, keep looking through all APKs.
        } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
            if (finalRes < 0) {
                finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
            }
        } else if (res >= 0) {
            if (finalRes < 0 || res < finalRes) {
                finalRes = res;
            }
        } else {
            // Unexpected error; bail
            return res;
        }
    }
    return finalRes;
}

helper 会遍历 base 和 split 的 native handle:没有 native code 时继续看其他 APK,找到匹配 ABI 时保留候选列表中更靠前的索引,遇到未知负值立即返回。返回值是 ABI 数组索引或错误码,不是 ABI 字符串;调用方必须使用同一 supportedAbis 数组解释索引。

4.3 复制与 increment ​

源码文件:frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

java
public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
        String[] abiList, boolean useIsaSubdir, boolean isIncremental) throws IOException {
    int abi = findSupportedAbi(handle, abiList);
    if (abi < 0) {
        return abi;
    }

    final String supportedAbi = abiList[abi];
    final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
    final File subDir = useIsaSubdir
            ? new File(libraryRoot, instructionSet) : libraryRoot;

    if (isIncremental) {
        int res = incrementalConfigureNativeBinariesForSupportedAbi(
                handle, subDir, supportedAbi);
        if (res != PackageManager.INSTALL_SUCCEEDED) {
            return res;
        }
        return abi;
    }

    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;
}

复制方法先探测 ABI,再按 instruction set 决定目标子目录。incremental 包走 incrementalConfigureNativeBinariesForSupportedAbi(),普通包创建目录并调用 native copy。注释明确指出:extractNativeLibs=false 时仍要在 native 层检查库有效性;“不解压”不等于“没有 native library policy”。

4.4 清理目录 ​

源码文件:frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

java
public static void removeNativeBinariesLI(String nativeLibraryPath) {
    if (nativeLibraryPath == null) return;
    removeNativeBinariesFromDirLI(new File(nativeLibraryPath), false /* delete root dir */);
}

public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot,
        boolean deleteRootDir) {
    if (nativeLibraryRoot.exists()) {
        final File[] files = nativeLibraryRoot.listFiles();
        if (files != null) {
            for (int nn = 0; nn < files.length; nn++) {
                if (files[nn].isDirectory()) {
                    removeNativeBinariesFromDirLI(files[nn], true /* delete root dir */);
                } else if (!files[nn].delete()) {
                    Slog.w(TAG, "Could not delete native binary: " + files[nn].getPath());
                }
            }
        }
        // Do not delete 'lib' directory itself unless specifically asked.
        if (deleteRootDir && !nativeLibraryRoot.delete()) {
            Slog.w(TAG, "Could not delete native binary directory: "
                    + nativeLibraryRoot.getPath());
        }
    }
}

卸载清理默认删除目录内容但保留根目录,避免后续更新无法复用安装目录;递归子目录时才传 deleteRootDir=true。ScanPackageUtils 把 nativeLibraryRootDir 写入 PackageSetting.legacyNativeLibraryPath,所以清理 owner 读取的是 Settings 保存的 root,而不是临时 ParsedPackage 字段。

5. 扫描写回 ​

5.1 ABI 与 Settings ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java

java
pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
        .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
        .setCpuAbiOverride(cpuAbiOverride);

if (DEBUG_ABI_SELECTION) {
    Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
            + " to root=" + parsedPackage.getNativeLibraryRootDir()
            + ", to dir=" + parsedPackage.getNativeLibraryDir()
            + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
}

// Push the derived path down into PackageSettings so we know what to
// clean up at uninstall time.
pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());

ABI 和路径写回分两步:ABI 从 ParsedPackage 的 raw fields 写入设置,native library root 单独写入 legacy path。后者的注释直接说明消费者是卸载清理;如果只修改 ApplicationInfo,卸载时可能找不到需要删除的目录。

5.2 shared user ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java

java
public String getAdjustedAbiForSharedUser(
        ArraySet<? extends PackageStateInternal> packagesForUser,
        AndroidPackage scannedPackage) {
    String requiredInstructionSet = null;
    if (scannedPackage != null) {
        String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(scannedPackage);
        if (pkgRawPrimaryCpuAbi != null) {
            requiredInstructionSet = VMRuntime.getInstructionSet(pkgRawPrimaryCpuAbi);
        }
    }

    PackageStateInternal requirer = null;
    for (PackageStateInternal ps : packagesForUser) {
        if (scannedPackage != null && scannedPackage.getPackageName().equals(
                ps.getPackageName())) {
            continue;
        }
        if (ps.getPrimaryCpuAbiLegacy() == null) {
            continue;
        }

        final String instructionSet = VMRuntime.getInstructionSet(ps.getPrimaryCpuAbiLegacy());
        if (requiredInstructionSet != null && !requiredInstructionSet.equals(instructionSet)) {
            Slog.w(PackageManagerService.TAG, "Instruction set mismatch, "
                    + ((requirer == null) ? "[caller]" : requirer)
                    + " requires " + requiredInstructionSet + " whereas " + ps
                    + " requires " + instructionSet);
        }

        if (requiredInstructionSet == null) {
            requiredInstructionSet = instructionSet;
            requirer = ps;
        }
    }

    if (requiredInstructionSet == null) {
        return null;
    }
    return requirer != null ? requirer.getPrimaryCpuAbiLegacy()
            : AndroidPackageUtils.getRawPrimaryCpuAbi(scannedPackage);
}

shared user 只协调 primary instruction set,接口注释明确说 secondary ABI 不匹配。正在更新的包会跳过自身旧状态,避免旧 ABI 强行约束新包;如果集合中已有包先提出 instruction set,新的 scanned package 会被调整到该 ABI,反之则用新包调整集合。源码只打印 mismatch warning,不在这里抛异常。

5.3 16 KB 对齐 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java、frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java

java
if (Flags.appCompatOption16kb() && (is16KbDevice || request.mEnableAlignmentChecks)) {
    if (parsedPackage.getPageSizeAppCompatFlags()
            > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
        pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags());
    }

    if ((Build.SUPPORTED_64_BIT_ABIS.length > 0)
            && !isSystemApp && !isApex && !isPlatformPackage) {
        NativeLibraryHelper.AlignmentResult res = packageAbiHelper.checkPackageAlignment(
                parsedPackage, pkgSetting.getLegacyNativeLibraryPath(),
                parsedPackage.isNativeLibraryRootRequiresIsa(),
                pkgSetting.getCpuAbiOverride());
        if (res != null && res.unalignedLibraries != null
                && res.flags >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
            pkgSetting.setPageSizeAppCompatFlags(res.flags);
            pkgSetting.setLibraryAlignmentInfo(res.unalignedLibraries);
        }
    }
}

对齐检查受 feature flag、16 KB 设备或显式 alignment 开关控制;system、APEX、platform package 被排除在普通应用检查之外。NativeLibraryHelper.checkAlignmentForCompatMode() 只检查匹配到的 64 位 ABI,并返回 flags 和未对齐库列表;它不是 ABI 选择器,也不能替代 findSupportedAbi()。

6. 测试与诊断 ​

6.1 ABI 路径测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

java
@Before
public void setupDefaultAbiBehavior() throws Exception {
    when(mMockPackageAbiHelper.derivePackageAbi(
            any(AndroidPackage.class), anyBoolean(), anyBoolean(), nullable(String.class),
            any(File.class)))
            .thenReturn(new Pair<>(
                    new PackageAbiHelper.Abis("derivedPrimary", "derivedSecondary"),
                    new PackageAbiHelper.NativeLibraryPaths(
                            "derivedRootDir", true, "derivedNativeDir", "derivedNativeDir2")));
    when(mMockPackageAbiHelper.deriveNativeLibraryPaths(
            any(AndroidPackage.class), anyBoolean(), anyBoolean(), any(File.class)))
            .thenReturn(new PackageAbiHelper.NativeLibraryPaths(
                    "getRootDir", true, "getNativeDir", "getNativeDir2"));
    when(mMockPackageAbiHelper.getBundledAppAbis(any(AndroidPackage.class)))
            .thenReturn(new PackageAbiHelper.Abis("bundledPrimary", "bundledSecondary"));
}

private static void assertAbiAndPathssDerived(ScanResult scanResult) {
    PackageSetting pkgSetting = scanResult.mPkgSetting;
    final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
            pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
    assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
    assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
    assertThat(applicationInfo.nativeLibraryRootDir, is("derivedRootDir"));
    assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("derivedRootDir"));
    assertThat(applicationInfo.nativeLibraryDir, is("derivedNativeDir"));
    assertThat(applicationInfo.secondaryNativeLibraryDir, is("derivedNativeDir2"));
}

测试通过 mock 的 PackageAbiHelper 把 ABI 和路径结果固定下来,断言 ScanPackageUtils 是否把它们同时写进 ApplicationInfo 和 PackageSetting。它证明的是写回链路,不证明真实 APK 中 .so 的 ELF ABI 或 native copy 行为。

6.2 APEX 与复用 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

java
@Test
public void scanFirstBoot_apexDontDeriveAbis() throws Exception {
    final PackageSetting pkgSetting = createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
            .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build();
    final String codePath = "/data/apex/" + DUMMY_PACKAGE_NAME + ".apex";
    final ParsedPackage basicPackage =
            ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
                    mock(TypedArray.class), false, null)
                    .setVolumeUuid(UUID_ONE.toString()).hideAsParsed())
                    .setVersionCodeMajor(1).setVersionCode(2345).setSystem(true);

    final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
            .setPkgSetting(pkgSetting)
            .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
            .build());

    final PackageSetting resultSetting = scanResult.mPkgSetting;
    final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
            resultSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, resultSetting);
    assertThat(applicationInfo.primaryCpuAbi, nullValue());
    assertThat(applicationInfo.nativeLibraryRootDir, nullValue());
}

@Test
public void scanFirstBoot_derivesAbis() throws Exception {
    final PackageSetting pkgSetting =
            createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
    final ParsedPackage basicPackage =
            ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME).hideAsParsed());

    final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
            .setPkgSetting(pkgSetting)
            .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE)
            .build());

    assertAbiAndPathssDerived(scanResult);
}

两个测试构成对照:带 SCAN_AS_APEX 时 ABI 和 native path 保持 null;普通 first boot 则调用 derive 并得到 mock 的 ABI/path。它们证明 flag 选择了不同分支,不证明真实 APEX payload 的 native library 可加载。

6.3 错误码对照 ​

返回值/异常来源语义后续处理
NO_NATIVE_LIBRARIESfindSupportedAbi()/copyAPK 没有 native code通常允许;有 ABI override 时仍记录 override
INSTALL_FAILED_NO_MATCHING_ABIShelper有 native code 但候选 ABI 不匹配普通包升级为内部错误;multiArch 可宽容或按 forceMatch 失败
INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABISmaybeThrowExceptionForMultiArchCopy()强制 multiArch 未覆盖全部原生 ABI抛 PackageManagerException,扫描/安装失败
INSTALL_FAILED_CPU_ABI_INCOMPATIBLERenderScript 32 位要求64 位-only 设备无法运行 bitcode直接失败
INSTALL_FAILED_INTERNAL_ERRORcopy/共享库约束native copy 错误或 shared library 非 multiarch终止扫描/安装
IOExceptionHandle.create()、目录创建APK 打开或目标目录失败由 helper 转换/记录,finally 关闭 handle

不要把 NO_NATIVE_LIBRARIES 当成失败;它只表示没有 .so。也不要把 helper 返回的 ABI 索引直接当错误码,因为 Android 17 的接口仍用同一个整数返回索引和负错误码,源码注释中的 TODO 也指出了这种设计风险。

6.4 只读诊断 ​

text
adb shell dumpsys package <package-name>
adb shell logcat -s PackageManager PackageManagerService NativeLibraryHelper
adb shell ls -l <nativeLibraryRootDir>

诊断顺序建议是:

  1. 先看 code path 是 bundled monolithic、cluster、data APK 还是 APEX;
  2. 对照 primaryCpuAbi/secondaryCpuAbi 与设备 SUPPORTED_ABIS;
  3. 检查 nativeLibraryRootDir、nativeLibraryDir、secondaryNativeLibraryDir 是否符合路径模型;
  4. 查看 extractNativeLibs、multiArch、ABI override 和 incremental 状态;
  5. 发生失败时区分“没有 native code”“无匹配 ABI”“复制失败”“对齐失败”;
  6. 卸载或更新后确认 legacyNativeLibraryPath 对应目录是否被清理或保留根目录。

7. 源码路线 ​

定位 native library 问题时,可按以下顺序阅读:

  1. ScanPackageUtils.scanPackageOnly():确认 needToDeriveAbi、SCAN_AS_APEX、SCAN_NEW_INSTALL 和 Settings 复用分支。
  2. PackageAbiHelperImpl.derivePackageAbi():确认 extractLibs、multiArch、RenderScript、ABI override 和 incremental 条件。
  3. deriveNativeLibraryPaths():根据 code path 判断 monolithic/cluster 和 system/data 路径。
  4. getBundledAppAbi():确认预装分区中 lib/lib64 目录如何反推 ABI。
  5. NativeLibraryHelper.Handle、findSupportedAbi()、copyNativeBinariesForSupportedAbi():确认 APK handle、ABI 索引、目录创建和 native copy。
  6. PackageAbiHelper.getAdjustedAbiForSharedUser():确认 shared UID 的 primary instruction set 调整。
  7. PackageSetting.setLegacyNativeLibraryPath() 与卸载 helper:确认清理消费者使用的路径来源。

一个实用练习是:给定一个 /data/app cluster 包,Manifest 声明 multiArch=true、extractNativeLibs=false,APK 同时含 arm64 和 armeabi-v7a 库,但设备没有原生 32 位 ABI。请分别判断 Handle 如何打开 base/split、helper 会调用 findSupportedAbi() 还是 copy、primary/secondary 如何设置、是否会因为 target SDK 的 forceMatch 失败,以及卸载时哪个字段提供清理路径。

8. 设计收束 ​

Android 17 的 native-libs 扫描是一条“路径策略 → ABI 探测 → 设置写回 → 清理消费”链路:

  • bundled system app 通过分区下的 per-package lib/lib64 目录推断 ABI;data/updated app 才通常从 APK 内容选择并可能提取 native libraries。
  • monolithic 与 cluster 的 nativeLibraryRootDir、ISA 子目录和 secondary path 不同,不能用单一目录模板解释。
  • extractNativeLibs=false 仍要验证 native code;multiArch 还要分别处理 32/64 位候选和 target SDK >= Vanilla Ice Cream 的原生 ABI 约束。
  • NO_NATIVE_LIBRARIES、无匹配 ABI、multiArch 全匹配失败、RenderScript 32 位限制和 copy I/O 是不同失败边界。
  • PackageAbiHelperImpl 负责策略,NativeLibraryHelper 负责 handle/JNI 文件操作,ScanPackageUtils 负责写回 ParsedPackage/PackageSetting,卸载 helper 读取 legacyNativeLibraryPath 清理目录。
  • 测试 mock 证明状态写回和 APEX/first boot 分支,真实设备 trace/log/dumpsys 才能进一步证明 ABI 探测、文件布局和加载结果。

后续专题会进入 RRO/overlay 包扫描,讨论 overlay 的 target、优先级、签名和分区策略如何在包扫描阶段与普通 APK policy 分开处理。