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.javaframeworks/base/services/core/java/com/android/server/pm/PackageAbiHelperImpl.javaframeworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java
| 对象 | owner | 负责内容 | 结果消费者 |
|---|---|---|---|
ParsedPackage | parser/扫描流程 | Manifest 中的 multiArch、extractNativeLibs、ABI 原始字段 | ABI 推导、ApplicationInfo |
PackageAbiHelperImpl | PMS 注入依赖 | 设备 ABI 选择、路径推导、shared user 调整 | ScanPackageUtils |
NativeLibraryHelper.Handle | native helper | 打开 base/split APK、保存 native handle 和包属性 | findSupportedAbi()、复制、对齐检查 |
PackageSetting | Settings | 持久化 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
@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
@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_LIBRARIES | findSupportedAbi()/copy | APK 没有 native code | 通常允许;有 ABI override 时仍记录 override |
INSTALL_FAILED_NO_MATCHING_ABIS | helper | 有 native code 但候选 ABI 不匹配 | 普通包升级为内部错误;multiArch 可宽容或按 forceMatch 失败 |
INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS | maybeThrowExceptionForMultiArchCopy() | 强制 multiArch 未覆盖全部原生 ABI | 抛 PackageManagerException,扫描/安装失败 |
INSTALL_FAILED_CPU_ABI_INCOMPATIBLE | RenderScript 32 位要求 | 64 位-only 设备无法运行 bitcode | 直接失败 |
INSTALL_FAILED_INTERNAL_ERROR | copy/共享库约束 | native copy 错误或 shared library 非 multiarch | 终止扫描/安装 |
IOException | Handle.create()、目录创建 | APK 打开或目标目录失败 | 由 helper 转换/记录,finally 关闭 handle |
不要把 NO_NATIVE_LIBRARIES 当成失败;它只表示没有 .so。也不要把 helper 返回的 ABI 索引直接当错误码,因为 Android 17 的接口仍用同一个整数返回索引和负错误码,源码注释中的 TODO 也指出了这种设计风险。
6.4 只读诊断
adb shell dumpsys package <package-name>
adb shell logcat -s PackageManager PackageManagerService NativeLibraryHelper
adb shell ls -l <nativeLibraryRootDir>诊断顺序建议是:
- 先看 code path 是 bundled monolithic、cluster、data APK 还是 APEX;
- 对照
primaryCpuAbi/secondaryCpuAbi与设备SUPPORTED_ABIS; - 检查
nativeLibraryRootDir、nativeLibraryDir、secondaryNativeLibraryDir是否符合路径模型; - 查看
extractNativeLibs、multiArch、ABI override 和 incremental 状态; - 发生失败时区分“没有 native code”“无匹配 ABI”“复制失败”“对齐失败”;
- 卸载或更新后确认
legacyNativeLibraryPath对应目录是否被清理或保留根目录。
7. 源码路线
定位 native library 问题时,可按以下顺序阅读:
ScanPackageUtils.scanPackageOnly():确认needToDeriveAbi、SCAN_AS_APEX、SCAN_NEW_INSTALL和 Settings 复用分支。PackageAbiHelperImpl.derivePackageAbi():确认extractLibs、multiArch、RenderScript、ABI override 和 incremental 条件。deriveNativeLibraryPaths():根据 code path 判断 monolithic/cluster 和 system/data 路径。getBundledAppAbi():确认预装分区中lib/lib64目录如何反推 ABI。NativeLibraryHelper.Handle、findSupportedAbi()、copyNativeBinariesForSupportedAbi():确认 APK handle、ABI 索引、目录创建和 native copy。PackageAbiHelper.getAdjustedAbiForSharedUser():确认 shared UID 的 primary instruction set 调整。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 分开处理。
