Manifest 解析入口
本文面向已经读过 PackageParser2 架构 和 增量扫描 的读者。前文解释了 parser 门面、ThreadLocal 和 cache;本文把镜头推进到 Android 17 的 Manifest 入口,追踪一个文件或目录如何经过 PackageLite、AssetLoader、base/split XML parser,最终生成 ParsingPackage。
Android 17 把入口拆成三层:ApkLiteParseUtils 做轻量结构检查,ParsingPackageUtils 创建资源加载器并解析完整 Manifest,PMS 再负责签名、ABI、scan policy、PackageSetting 和注册。本文不重复四大组件的全部属性,而是解释入口选择、base/split 汇合、flags 传递、资源释放和错误返回。
读完后,读者应能从 PackageParser2.parsePackage 进入 parseMonolithicPackage 或 parseClusterPackage;判断目录为什么被视为 cluster、缺少 base APK 在哪一层失败、isolated split 依赖树由谁构造、PARSE_COLLECT_CERTIFICATES 为什么不属于 Lite 阶段;还能区分轻量解析、完整 Manifest、split 资源和签名收集的失败边界。
1. 入口分层
1.1 文件与目录
源码文件:
- frameworks/base/core/java/com/android/internal/pm/parsing/PackageParser2.java
- frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
- frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
| 输入 | Lite 入口 | 完整入口 | 结果 |
|---|---|---|---|
| 单个 APK 文件 | parseMonolithicPackageLite | parseMonolithicPackage | 一个 base package |
| 包目录 | parseClusterPackageLite | parseClusterPackage | base + 多个 split |
| 已有 PackageLite | 不重新读 APK | parsePackageFromPackageLite | 默认字段的 package |
文件/目录形态只决定第一层分发,不决定签名是否已经验证。PackageParser2 的 parsePackage 返回 ParsedPackage 前会调用 hideAsParsed;证书兼容性仍由 PMS 扫描/安装阶段负责。
1.2 Lite 与完整对象
PackageLite 保存 package path、base path、split names、split paths、revision codes、split dependency 元数据和少量 Manifest 属性。完整 ParsingPackage/ParsedPackage 才承载 application、权限、组件、库依赖和派生字段。Lite 成功只能证明入口信息可读,不能证明 Manifest 的所有标签合法。
1.3 资源所有权
SplitAssetLoader、AssetManager、XmlResourceParser 和 TypedArray 都是一次 parse 的局部资源。ParsingPackageUtils 在对应方法的 finally 或 try-with-resources 中关闭它们;PackageParser2.close 只清理 ThreadLocal。
2. Lite 预解析
2.1 统一分发
源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
File packageFile, int flags) {
if (packageFile.isDirectory()) {
return parseClusterPackageLite(input, packageFile, flags);
} else {
return parseMonolithicPackageLite(input, packageFile, flags);
}
}Lite 分发与完整 parser 分发保持一致,但返回类型是 PackageLite。它让后续代码在打开完整资源前确认包形态和 split 元数据。
2.2 单体 APK
源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
public static ParseResult<PackageLite> parseMonolithicPackageLite(
ParseInput input, File packageFile, int flags) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
try {
final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
if (result.isError()) {
return input.error(result);
}
final ApkLite baseApk = result.getResult();
final String packagePath = packageFile.getAbsolutePath();
return input.success(new PackageLite(packagePath, baseApk.getPath(), baseApk,
null, null, null, null, null, null,
baseApk.getTargetSdkVersion(), null, null));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}单体结果的 split 数组为空,base APK 同时是输入文件和 base code path。Lite 阶段可能读取 Manifest 的轻量字段,但不会替代完整 parseBaseApk。
2.3 Cluster 校验
源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
public static ParseResult<PackageLite> parseClusterPackageLite(
ParseInput input, File packageDir, int flags) {
final File[] files = packageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
}
if (files.length == 1 && files[0].isDirectory()) {
return parseClusterPackageLite(input, files[0], flags);
}
String packageName = null;
int versionCode = 0;
ApkLite baseApk = null;
final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
try {
for (File file : files) {
if (!isApkFile(file)) {
continue;
}
final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
if (result.isError()) {
return input.error(result);
}
final ApkLite lite = result.getResult();
if (packageName == null) {
packageName = lite.getPackageName();
versionCode = lite.getVersionCode();
} else {
if (!packageName.equals(lite.getPackageName())) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Inconsistent package " + lite.getPackageName()
+ " in " + file + "; expected " + packageName);
}
if (versionCode != lite.getVersionCode()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Inconsistent version " + lite.getVersionCode()
+ " in " + file + "; expected " + versionCode);
}
}
ApkLite prev = apks.put(lite.getSplitName(), lite);
if (prev != null) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Split name " + lite.getSplitName()
+ " defined more than once; most recent was " + file);
}
}
baseApk = apks.remove(null);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
return composePackageLiteFromApks(input, packageDir, baseApk, apks);
}cluster Lite 的校验顺序是空目录、嵌套目录归一化、非 APK 过滤、包名一致性、versionCode 一致性、split name 唯一性,最后由 composePackageLiteFromApks 检查 base 是否存在。
2.4 split 排序
源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
public static ParseResult<PackageLite> composePackageLiteFromApks(
ParseInput input, File packageDir, ApkLite baseApk,
ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
if (baseApk == null) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Missing base APK in " + packageDir);
}
final int size = ArrayUtils.size(splitApks);
String[] splitNames = null;
String[] splitCodePaths = null;
int[] splitRevisionCodes = null;
if (size > 0) {
splitNames = new String[size];
splitCodePaths = new String[size];
splitRevisionCodes = new int[size];
splitNames = splitApks.keySet().toArray(splitNames);
Arrays.sort(splitNames, sSplitNameComparator);
for (int i = 0; i < size; i++) {
final ApkLite apk = splitApks.get(splitNames[i]);
splitCodePaths[i] = apkRenamed
? new File(packageDir, splitNameToFileName(apk)).getAbsolutePath()
: apk.getPath();
splitRevisionCodes[i] = apk.getRevisionCode();
}
}
return input.success(/* PackageLite with sorted split arrays */);
}真实实现还填充 feature split、uses split、config split、required split types 和 split types。所有并行数组使用同一排序后的 split index,后续 loader 和 parseSplitApk 都依赖这个不变量。
3. 完整路径
3.1 monolithic 路径
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input,
File apkFile, int flags) {
int liteParseFlags = flags & ~PARSE_COLLECT_CERTIFICATES;
final ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
final PackageLite lite = liteResult.getResult();
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile,
apkFile.getCanonicalPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult().set32BitAbiPreferred(lite.isUse32bitAbi()));
} catch (IOException e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}Lite 阶段不收集证书,完整 base parse 才进入资源和 Manifest 标签。canonical path、loader 和 ParseResult 都有独立失败/清理路径。
3.2 cluster 路径
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input,
File packageDir, int flags) {
int liteParseFlags = 0;
if ((flags & PARSE_APK_IN_APEX) != 0) {
liteParseFlags |= PARSE_APK_IN_APEX;
}
if ((flags & PARSE_APEX) != 0) {
liteParseFlags |= PARSE_APEX;
}
final ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
final PackageLite lite = liteResult.getResult();
SparseArray<int[]> splitDependencies = null;
final SplitAssetLoader assetLoader;
if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
try {
splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
} catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
}
} else {
assetLoader = new DefaultSplitAssetLoader(lite, flags);
}
try {
final File baseApk = new File(lite.getBaseApkPath());
boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
lite.getPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
return input.error(result);
}
ParsingPackage pkg = result.getResult();
if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
pkg.asSplit(lite.getSplitNames(), lite.getSplitApkPaths(),
lite.getSplitRevisionCodes(), splitDependencies);
for (int i = 0; i < lite.getSplitNames().length; i++) {
final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
final ParseResult<ParsingPackage> split =
parseSplitApk(input, pkg, i, splitAssets, flags);
if (split.isError()) {
return input.error(split);
}
}
}
pkg.set32BitAbiPreferred(lite.isUse32bitAbi());
return input.success(pkg);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}cluster 的 isolated split dependency 非法会在 loader 创建前失败;base 成功后 split 逐个写入同一个 ParsingPackage,任一 split 失败都会使整个 cluster 失败。
3.3 base Manifest
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags,
boolean shouldSkipComponents)
throws XmlPullParserException, IOException {
ParseResult<Pair<String, String>> packageSplitResult =
ApkLiteParseUtils.parsePackageSplitNames(input, parser);
if (packageSplitResult.isError()) {
return input.error(packageSplitResult);
}
Pair<String, String> packageSplit = packageSplitResult.getResult();
final String pkgName = packageSplit.first;
final String splitName = packageSplit.second;
if (!TextUtils.isEmpty(splitName)) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
"Expected base APK, but found split " + splitName);
}
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
try {
final boolean isCoreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
final ParsingPackage pkg = mCallback.startParsingPackage(
pkgName, apkPath, codePath, manifestArray, isCoreApp);
final ParseResult<ParsingPackage> result = parseBaseApkTags(
input, pkg, manifestArray, res, parser, flags, shouldSkipComponents);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg);
} finally {
manifestArray.recycle();
}
}base 入口先确认 package/split name,再由 callback 创建 ParsingPackage,之后进入顶层标签循环。base APK 错带 split name 会返回 BAD_PACKAGE_NAME。
3.4 split Manifest
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseSplitApk(ParseInput input,
ParsingPackage pkg, int splitIndex, AssetManager assets,
XmlResourceParser parser, int flags) {
final String apkPath = pkg.getSplitCodePaths()[splitIndex];
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
try (XmlResourceParser splitParser = assets.openXmlResourceParser(
cookie, ANDROID_MANIFEST_FILENAME, false)) {
Resources res = new Resources(assets, mDisplayMetrics, null);
ParseResult<ParsingPackage> parseResult = parseSplitApk(
input, pkg, res, splitParser, flags, splitIndex);
if (parseResult.isError()) {
return input.error(parseResult.getErrorCode(),
apkPath + " (at " + splitParser.getPositionDescription() + "): "
+ parseResult.getErrorMessage());
}
return parseResult;
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
}
}split parser 不创建新的 package 对象,而是把 split application/classloader/components 写入 base package。cookie、XML 和标签错误都会附加 split path。
4. 标签分发
4.1 base 标签循环
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
TypedArray sa, Resources res, XmlResourceParser parser, int flags,
boolean shouldSkipComponents) throws XmlPullParserException, IOException {
ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
if (sharedUserResult.isError()) {
return sharedUserResult;
}
pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
R.styleable.AndroidManifest_installLocation, sa))
.setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
R.styleable.AndroidManifest_targetSandboxVersion, sa))
.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
int type;
boolean foundApp = false;
final int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String tagName = parser.getName();
final ParseResult result;
if (TAG_APPLICATION.equals(tagName)) {
if (foundApp) {
result = RIGID_PARSER
? input.error("<manifest> has more than one <application>")
: input.success(null);
} else {
foundApp = true;
result = parseBaseApplication(input, pkg, res, parser, flags,
shouldSkipComponents);
}
} else {
result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
}
if (result.isError()) {
return input.error(result);
}
}
if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
ParseResult<?> deferResult = input.deferError(
"<manifest> does not contain an <application> or <instrumentation>",
DeferredError.MISSING_APP_TAG);
if (deferResult.isError()) {
return input.error(deferResult);
}
}
return validateBaseApkTags(input, pkg, flags);
}base 循环把 application 交给专用 parser,把其他顶层标签交给 parseBaseApkTag。缺 application 时先创建 deferred error,最后由 validateBaseApkTags 决定是否失败。
4.2 顶层路由
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult parseBaseApkTag(String tag, ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
switch (tag) {
case TAG_OVERLAY:
return parseOverlay(input, pkg, res, parser);
case TAG_KEY_SETS:
return parseKeySets(input, pkg, res, parser);
case TAG_PERMISSION_GROUP:
return parsePermissionGroup(input, pkg, res, parser);
case TAG_PERMISSION:
return parsePermission(input, pkg, res, parser, flags);
case TAG_PERMISSION_TREE:
return parsePermissionTree(input, pkg, res, parser);
case TAG_USES_PERMISSION:
case TAG_USES_PERMISSION_SDK_M:
return parseUsesPermission(input, pkg, res, parser);
case TAG_USES_FEATURE:
return parseUsesFeature(input, pkg, res, parser);
case TAG_USES_SDK_LIBRARY:
return parseUsesSdkLibrary(input, pkg, res, parser);
case TAG_USES_STATIC_LIBRARY:
return parseUsesStaticLibrary(input, pkg, res, parser);
case TAG_USES_LIBRARY:
return parseUsesLibrary(input, pkg, res, parser);
default:
return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
}
}未知标签由 ParsingUtils.unknownTag 处理,具体是 warning、跳过还是 error 取决于 parser 配置和标签上下文。
4.3 application 分支
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
boolean shouldSkipComponents) throws XmlPullParserException, IOException {
final String pkgName = pkg.getPackageName();
int targetSdk = pkg.getTargetSdkVersion();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
if (sa == null) {
return input.error("<application> does not contain any attributes");
}
String name = sa.getNonConfigurationString(
R.styleable.AndroidManifestApplication_name, 0);
if (name != null) {
String className = buildClassName(pkgName, name, input);
if (className == null) {
return input.error("Invalid application class name " + name);
}
pkg.setClassName(className);
}
parseBaseAppBasicFlags(pkg, sa, flags, targetSdk);
// child tags and main components are parsed here
return input.success(pkg);
} finally {
sa.recycle();
}
}application parser 负责 application class、基础 flags、child tags 和组件入口。shouldSkipComponents 只影响特定 SDK library 条件,不代表跳过 application 基础属性。
4.4 split app
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser,
int flags, int splitIndex) throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
pkg.setSplitHasCode(splitIndex, sa.getBoolean(
R.styleable.AndroidManifestApplication_hasCode, true));
final String classLoaderName = sa.getString(
R.styleable.AndroidManifestApplication_classLoader);
if (classLoaderName == null
|| ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
pkg.setSplitClassLoaderName(splitIndex, classLoaderName);
} else {
return input.error("Invalid class loader name: " + classLoaderName);
}
} finally {
sa.recycle();
}
String defaultSplitName = pkg.getSplitNames()[splitIndex];
// main components and split child tags use defaultSplitName
return input.success(pkg);
}split application 的字段集合比 base 窄,重点是 hasCode、class loader 和组件默认 split。非法 class loader 会使整个 cluster parse 失败。
5. flags 与证书
5.1 Lite flags
parseMonolithicPackage 会清除 PARSE_COLLECT_CERTIFICATES;cluster Lite 只保留 PARSE_APK_IN_APEX 和 PARSE_APEX。这使结构识别可以快速完成,而签名收集留给独立的 getSigningDetails。
5.2 证书入口
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
ParsedPackage pkg, boolean skipVerify) {
return getSigningDetails(input, pkg.getBaseApkPath(), pkg.isStaticSharedLibrary(),
pkg.getTargetSdkVersion(), pkg.getSplitCodePaths(), skipVerify);
}parseBaseApk 成功不等于签名兼容;PMS 仍会基于 PackageSetting、disabled setting 和 shared user 比较证书。
5.3 APEX 条件
APEX flags 会改变 Lite parser 读取的字段和完整 Manifest 校验;split dependency、APEX permission 和 code path 有专门分支。APEX 内 APK 的签名、分区 flags 和最终注册由 PMS/APEX owner 补充。
6. 错误与清理
6.1 错误表
| 层 | 条件 | 错误 |
|---|---|---|
| cluster Lite | 空目录 | INSTALL_PARSE_FAILED_NOT_APK |
| cluster Lite | 包名/versionCode 不一致 | INSTALL_PARSE_FAILED_BAD_MANIFEST |
| compose | 缺少 base APK | INSTALL_PARSE_FAILED_BAD_MANIFEST |
| split dependency | isolated split 非法 | INSTALL_PARSE_FAILED_BAD_MANIFEST |
| base | base 带 split name | INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME |
| split asset | cookie 为 0 | INSTALL_PARSE_FAILED_BAD_MANIFEST |
| path | canonical path 失败 | INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION |
| resource/XML | 未预期异常 | INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION |
6.2 失败对象
base 或任一 split 失败时,loader、XML parser、TypedArray 都应关闭;ParsingPackage 不会进入 hideAsParsed,也不会交给 PMS 的签名/ABI/注册流程。
6.3 cache 与重试
cache hit 不会进入完整 parser;cache miss 后完整 parser 失败,PMS 回到 ParseResult error。cache 损坏由服务端 PackageCacher 删除 entry 并返回 miss,入口 parser 本身不负责修复 cache 文件。
7. 测试与诊断
7.1 结构测试
源码文件:
- frameworks/base/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
- frameworks/base/core/tests/coretests/src/com/android/internal/pm/parsing/PackageParser2Test.java
测试输入和断言应覆盖:单 APK 的 base path 与空 split 数组;cluster 的 package/version 一致和 split 排序;缺 base、重复 split、空目录、一层嵌套目录;isolated split 非法 dependency;cache hit 跳过完整解析、cache miss 成功写回。它们证明入口结构和错误边界,不证明 PMS 后续签名、ABI 或组件注册。
7.2 并行入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java
pr.parsedPackage = parsePackage(scanFile, scanParams.parseFlags);并行 parser 共享 PackageParser2 实例,但每个线程通过 ThreadLocal 获得自己的 ParseTypeImpl。测试应断言并发任务的 ParseResult 文件和异常彼此独立,不应跨线程复用一个 ParseInput。
7.3 Trace 与命令
adb shell atrace --async_start package_manager sched freq idle
adb shell logcat -s PackageParser PackageManager PackageCacher
adb shell atrace --async_stop -z -o /data/local/tmp/manifest-parse.html
adb shell dumpsys package <package-name>搜索 parseApkLite、parsePackage、parseBaseApk、parseSplitApk。只有 Lite 区段可能表示 Lite 失败或 cache hit;base 成功、split 失败时应检查 split path、cookie 和 loader;parse 成功但 dumpsys 没有包时,要继续看签名/reconcile/冲突阶段。
8. 源码路线
定位 Manifest 解析问题时,按以下顺序阅读:
- PackageParser2.parsePackage:cache、嵌套目录归一化和文件/目录分发。
- ApkLiteParseUtils.parseMonolithicPackageLite/parseClusterPackageLite:包名、version、split、base 校验。
- composePackageLiteFromApks:split 排序、路径和 revision code。
- ParsingPackageUtils.parseMonolithicPackage/parseClusterPackage:loader、APEX flags、isolated split 和 close。
- parseBaseApk:Manifest 根标签、package/split name 和 package 创建。
- parseSplitApk:cookie、split parser、错误 path/position。
- parseBaseApkTags/parseBaseApkTag:顶层标签路由和 deferred error。
- parseBaseApplication/parseSplitApplication:application 字段和 split 限制。
- getSigningDetails:证书收集与 Manifest parse 的边界。
- PackageParser2.hideAsParsed 与 PMS scan:解析结果何时交给 ScanRequest。
一个实用练习是:目录包含 base.apk 和两个 split,其中一个 split 的 package name 不同,且 cache 中存在旧结果。请判断 cache 是否可能命中、Lite parser 在哪一步失败、loader 是否创建、hideAsParsed 是否调用、哪些资源需要关闭,以及 PMS 是否有机会进入签名校验。
9. 设计收束
Android 17 的 Manifest 入口是一条分层链:
- PackageParser2 负责 cache、ThreadLocal 和异常外壳;ApkLiteParseUtils 负责轻量结构;ParsingPackageUtils 负责完整 Manifest。
- monolithic 使用单 APK Lite、default loader 和 base parser;cluster 先校验 package/version/split/base,再选择 default 或 isolated split loader。
- base 与 split 写入同一个 ParsingPackage,split index 由 Lite compose 的 comparator 确定。
- PARSE_COLLECT_CERTIFICATES 不属于 Lite 结构阶段;签名收集由独立入口完成。
- loader、XML parser、TypedArray 和 ThreadLocal 各自有关闭/回收 owner;PackageParser2.close 不能替代局部资源清理。
- 空目录、缺 base、包名/version 不一致、重复 split、非法 dependency、错误 cookie 和 Manifest 异常会在不同层返回不同错误;失败对象不会进入 PMS 注册。
后续专题会深入四大组件解析,继续沿 parseBaseApplication 和 split child tag 路由,说明 Activity、Service、Provider、Receiver 如何进入 PackageImpl 数据模型。
