解析错误处理
本文是包解析单元的收束篇,承接 Manifest 解析入口、IntentFilter 解析、Split APK 解析 和 共享库依赖解析。前文分别讲了标签如何解析;本文只回答错误如何在这些函数之间传播、何时从“潜在错误”变成真正失败,以及外层 API 如何把返回值重新转换成异常。
Android 17 的现代解析路径使用 ParseInput/ParseResult,而不是让每个内部函数都抛出异常。ParseTypeImpl 同时实现这两个接口,并按线程复用;PackageParser2 在最外层把 error result 转成 PackageParserException。这不是两套互相竞争的解析器,而是“内部返回值、边界异常”的分层设计。
1. 错误状态图
1.1 解析层级
错误码、错误消息和原始异常一起向上移动;父函数不重新解释错误,只通过 input.error(result) 把它转换为自己的泛型类型。成功结果也可以被父函数直接返回,错误与成功都不需要 Java 异常栈展开。
1.2 三种“成功”
| 结果 | isSuccess() | getResult() | 典型来源 |
|---|---|---|---|
| 有对象成功 | true | 非 null | 正常组件、包对象 |
| 兼容性空成功 | true | null | 空 action filter、宽松不支持类型 |
| 失败 | false | 不应解包 | 缺属性、Manifest 错误、I/O |
ParseResult.isSuccess() 不保证 getResult() 非 null。读 Android 17 源码时,必须同时检查状态和对象,否则会把兼容性跳过误判为有效解析对象。
2. 接口契约
2.1 ParseResult
源码文件:frameworks/base/core/java/android/content/pm/parsing/result/ParseResult.java
public interface ParseResult<ResultType> {
boolean isSuccess();
boolean isError();
ResultType getResult();
int getErrorCode();
@Nullable String getErrorMessage();
@Nullable Exception getException();
}ParseResult 是输出视图,包含四类信息:状态、成功对象、安装解析错误码、可读消息和底层异常。接口注释还明确指出 ParseInput 与 ParseResult 实际上是同一个线程局部对象,只是通过泛型转换出不同视图。
2.2 ParseInput
源码文件:frameworks/base/core/java/android/content/pm/parsing/result/ParseInput.java
public interface ParseInput {
<ResultType> ParseResult<ResultType> success(ResultType result);
ParseResult<?> deferError(@NonNull String parseError, long deferredError);
ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion);
<ResultType> ParseResult<ResultType> skip(@NonNull String parseError);
<ResultType> ParseResult<ResultType> error(int parseError);
<ResultType> ParseResult<ResultType> error(@NonNull String parseError);
<ResultType> ParseResult<ResultType> error(int parseError,
@Nullable String errorMessage);
<ResultType> ParseResult<ResultType> error(int parseError,
@Nullable String errorMessage, @Nullable Exception exception);
<ResultType> ParseResult<ResultType> error(ParseResult<?> result);
}接口把错误构造集中到输入对象,解析函数只需返回 ParseResult。error(String) 默认使用 INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;skip() 则使用 INSTALL_PARSE_FAILED_SKIPPED,表示调用方可以忽略该包,而不是包格式一定损坏。
3. ParseTypeImpl
3.1 状态字段
源码文件:frameworks/base/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
@NonNull
private final Callback mCallback;
private Object mResult;
private int mErrorCode = PackageManager.INSTALL_SUCCEEDED;
@Nullable
private String mErrorMessage;
@Nullable
private Exception mException;
@Nullable
private ArrayMap<Long, String> mDeferredErrors = null;
private String mPackageName;
private int mTargetSdkVersion = -1;
}一个 ParseTypeImpl 保存当前解析调用的全部状态。INSTALL_SUCCEEDED 是成功哨兵值;只要 error code 不是它,isError() 就为 true。deferred error 使用 ArrayMap<Long, String>,key 是 change ID,value 是第一次出现的错误消息。
3.2 reset 与线程复用
public ParseInput reset() {
mResult = null;
mErrorCode = PackageManager.INSTALL_SUCCEEDED;
mErrorMessage = null;
mException = null;
if (mDeferredErrors != null) {
// Reuse allocated storage because one parser may process many APKs.
mDeferredErrors.erase();
}
mTargetSdkVersion = -1;
return this;
}PackageParser2 通过 ThreadLocal<ParseTypeImpl> 复用对象。reset 会清掉上一次包的结果、错误和 target SDK,但保留已经分配的 map 容器,避免扫描大量 APK 时反复分配。复用前不 reset 会造成上一个包的错误状态污染下一个包。
3.3 成功与错误
@Override
public <ResultType> ParseResult<ResultType> success(ResultType result) {
if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
Slog.wtf(TAG, "Cannot set to success after set to error, was "
+ mErrorMessage, mException);
}
mResult = result;
//noinspection unchecked
return (ParseResult<ResultType>) this;
}
@Override
public <ResultType> ParseResult<ResultType> error(int errorCode,
@Nullable String errorMessage, Exception exception) {
mErrorCode = errorCode;
mErrorMessage = errorMessage;
mException = exception;
//noinspection unchecked
return (ParseResult<ResultType>) this;
}错误状态一旦写入,再调用 success 会记录 wtf;这保证父函数不会把已经失败的子结果覆盖成成功。反过来,连续 success 是允许的,因为解析循环可能在多个 child tag 后反复构造成功视图。
4. DeferredError
4.1 ChangeId 定义
源码文件:frameworks/base/core/java/android/content/pm/parsing/result/ParseInput.java
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
public static final long MISSING_APP_TAG = 150776642;
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173;
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
public static final long RESOURCES_ARSC_COMPRESSED = 132742131;
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
public static final long MISSING_EXPORTED_FLAG = 150232615;这些 ID 对应“缺 application”“空 action/category”“压缩 resources.arsc”“缺少 exported”等兼容规则。@EnabledAfter 只是默认 gating 信息,最终是否启用由 ParseInput.Callback.isChangeEnabled() 决定。
4.2 版本未知时登记
@Override
public ParseResult<?> deferError(@NonNull String parseError, long deferredError) {
if (mTargetSdkVersion != -1) {
if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) {
return success(null);
}
if (mCallback.isChangeEnabled(deferredError, mPackageName, mTargetSdkVersion)) {
return error(parseError);
}
if (mDeferredErrors == null) {
mDeferredErrors = new ArrayMap<>();
}
mDeferredErrors.put(deferredError, null);
return success(null);
}
if (mDeferredErrors == null) {
mDeferredErrors = new ArrayMap<>();
}
// Keep only the first message for each change ID.
mDeferredErrors.putIfAbsent(deferredError, parseError);
return success(null);
}target SDK 尚未确定时,错误只按 change ID 登记,不立即失败;同一个 change ID 只保留第一条消息。若 target SDK 已知,则当场询问 callback:change 开启就 error,关闭就 success(null)。
4.3 版本确定后触发
@Override
public ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion) {
mPackageName = packageName;
mTargetSdkVersion = targetSdkVersion;
int size = CollectionUtils.size(mDeferredErrors);
for (int index = size - 1; index >= 0; index--) {
long changeId = mDeferredErrors.keyAt(index);
String errorMessage = mDeferredErrors.valueAt(index);
if (mCallback.isChangeEnabled(changeId, mPackageName, mTargetSdkVersion)) {
return error(errorMessage);
} else {
// Keep the key to remember that this change was checked and disabled.
mDeferredErrors.setValueAt(index, null);
}
}
return success(null);
}enableDeferredError() 在解析到 uses-sdk 后调用。只要一个 change ID 被启用,就返回第一个触发的 error;全部关闭才返回 success(null)。关闭后的 key 仍保留,后续再次遇到同一 deferred error 不会重复询问兼容服务。
4.4 真实调用点
ParseResult<Integer> targetSdkVersionResult = parseTargetSdk(...);
if (targetSdkVersionResult.isError()) {
return input.error(targetSdkVersionResult);
}
int targetSdkVersion = targetSdkVersionResult.getResult();
ParseResult<?> deferResult = input.enableDeferredError(
pkg.getPackageName(), targetSdkVersion);
if (deferResult.isError()) {
return input.error(deferResult);
}target SDK 解析成功是 deferred error 生效的时机。此处必须先检查 parseTargetSdk,再 enable;如果 target SDK 本身解析失败,就不能用不完整的版本信息判断兼容规则。
5. 错误传播
5.1 错误向上冒泡
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
ParseResult<ParsingPackage> result = parseBaseApk(
input, baseApk, lite.getPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
return input.error(result);
}父函数不直接读取子结果的内部字段再重建错误,而是调用 input.error(result)。这会保留原始 error code、message 和 exception,同时满足当前方法的泛型返回类型。
5.2 标签级例子
ParseResult<Property> result = ParsingPackageUtils.parseMetaData(
pkg, component, resources, parser, "<meta-data>", input);
if (result.isError()) {
return input.error(result);
}
Property property = result.getResult();
if (property != null) {
component.setMetaData(property.toBundle(component.getMetaData()));
}meta-data 的调用方同时处理 error 和 success(null);后者在宽松模式下表示“不写入任何值”。IntentFilter、uses-library 和 split 解析也采用同一模式,因此每个 child tag 的调用方都必须明确处理 null 结果。
6. 错误码与边界
6.1 常见错误码
| 错误码 | 典型来源 | 语义 |
|---|---|---|
INSTALL_PARSE_FAILED_NOT_APK | APK/资源无法打开 | 输入不是可解析 APK |
INSTALL_PARSE_FAILED_BAD_MANIFEST | split 不一致、依赖非法 | Manifest 结构或组合非法 |
INSTALL_PARSE_FAILED_MANIFEST_MALFORMED | 缺属性、未知类型 | Manifest 内容不符合规则 |
INSTALL_PARSE_FAILED_NO_CERTIFICATES | 签名收集失败 | 没有满足要求的证书 |
INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED | deferred 资源表规则触发 | 高版本目标禁止压缩资源表 |
INSTALL_PARSE_FAILED_SKIPPED | input.skip() | 调用方应忽略该包 |
错误码是机器可读分类,错误消息才携带具体标签、路径和 XML 位置。排查时应同时保留两者。
6.2 具体规则与调用方
| 规则 | 登记位置 | 最终触发点 |
|---|---|---|
| 无 application | base/split Manifest 尾部 | target SDK 已知后 |
| 空 action/category | IntentFilter 子标签 | target SDK 已知后 |
缺 android:exported | Activity/Service 组件结束 | target SDK 已知后 |
| 压缩 resources.arsc | base APK 资源加载后 | target SDK 已知后 |
| 缺少 name/MIME/loader | 当前解析函数 | 立即返回 error |
7. 异常桥接
7.1 Parser2 边界
源码文件:frameworks/base/core/java/com/android/internal/pm/parsing/PackageParser2.java
ParseInput input = mSharedResult.get().reset();
ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();内部返回值在 PackageParser2.parsePackage() 这一边界重新变成 checked exception。调用方因此可以继续沿旧的 try/catch 结构工作,但错误码和原始异常没有丢失。
7.2 ParserException
源码文件:frameworks/base/core/java/com/android/internal/pm/parsing/PackageParserException.java
public class PackageParserException extends Exception {
public final int error;
public PackageParserException(int error, String detailMessage) {
super(detailMessage);
this.error = error;
}
public PackageParserException(int error, String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
this.error = error;
}
}这个类只有一个公开错误码字段和两个构造函数。它不负责分类、日志或恢复;这些职责属于产生错误的 parser 和捕获异常的安装/扫描调用方。
7.3 扫描与安装调用方
try {
return mPackageParser.parsePackage(scanFile, parseFlags, true);
} catch (PackageParserException e) {
throw new PackageManagerException(e.error, e.getMessage(), e);
}安装路径也采用相同的桥接方式,只是把异常转换成安装准备阶段的错误对象。
try {
parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
} catch (PackageParserException e) {
throw new PackageManagerException(e.error, e.getMessage(), e);
}ParallelPackageParser 和 InstallPackageHelper 都在边界捕获 PackageParserException,再转换为自己的错误类型。扫描与安装可以据此决定记录失败、返回安装码或停止当前任务,而不需要理解 ParseTypeImpl 的内部状态。
8. 失败、恢复与清理
8.1 资源关闭
try (XmlResourceParser parser = assets.openXmlResourceParser(
cookie, ANDROID_MANIFEST_FILENAME, false)) {
Resources res = new Resources(assets, mDisplayMetrics, null);
return parseSplitApk(input, pkg, res, parser, flags, splitIndex);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
}Manifest parser 使用 try-with-resources;异常会被转换成带路径的 ParseResult。Split 资源加载器在更外层 finally 中关闭 AssetManager,确保失败的 split 不留下打开的资源句柄。
8.2 缓存与失败恢复
PackageCacher 读取缓存失败时删除坏文件并返回 null,PackageParser2 随后走完整解析;写缓存失败则只记录日志,当前解析仍返回成功包。这说明解析错误和缓存错误的恢复策略不同:前者通常终止当前包,后者允许绕过加速层继续。
8.3 部分状态不回滚
解析器会在发现错误时停止向上返回,但不会在每个 setXxx() 后建立事务回滚。原因是只有最终成功的 ParsedPackage 才会交给扫描/安装提交;失败对象不再作为有效包消费。已经注册到全局 resolver 的旧包,则由替换/卸载流程负责移除,而不是由 ParseInput 负责。
9. 测试与诊断
9.1 输入与断言
| 输入 | 断言 | 证明范围 |
|---|---|---|
| 缺少 Manifest 必需标签 | error code 和 message 正确 | 立即失败 |
| target SDK 低于 gating 版本 | deferred error 最终 success(null) | 兼容路径 |
| target SDK 高于 gating 版本 | enableDeferredError 返回 error | 延迟触发 |
| 同一 deferred ID 多次出现 | 只保留第一次消息 | 去重逻辑 |
| 子函数 error | 父函数 error code/message 不变 | 冒泡 |
success(null) | 调用方不解包并继续 | 空成功 |
| Parcel/XML/I/O 异常 | 资源关闭,错误带原始 exception | 清理与因果链 |
| PackageParser2 error | 转成 PackageParserException | 异常桥接 |
input.skip() | 错误码为 SKIPPED | 可忽略包 |
测试要覆盖“错误产生、传播、触发、桥接”四个位置。只测试最终安装码,无法知道错误是在标签解析、target SDK gating 还是外层转换时丢失的。
9.2 诊断顺序
- 先记录
getErrorCode()、getErrorMessage()和getException(),不要只看日志首行。 - 回到产生 error 的标签函数,确认是立即 error 还是 deferred error。
- 若是 deferred error,检查
enableDeferredError()调用时的 package name、target SDK 和 callback。 - 沿父函数检查每一层是否执行
input.error(result),以及是否错误地调用了getResult()。 - 到
PackageParser2查看是否转成PackageParserException,再看扫描或安装调用方如何转换。 - 若涉及 split、APK assets 或 cache,确认 finally/try-with-resources 已执行,并区分“包失败”和“加速层回退”。
10. 源码路线
建议按以下顺序阅读:
ParseResult.java:理解成功、失败和空成功的契约。ParseInput.java:查看错误构造、skip 和 DeferredError ID。ParseTypeImpl.java:理解状态字段、reset、error 和 deferred map。ParsingPackageUtils.enableDeferredError调用点:确认 target SDK 何时生效。- 任意标签调用方(如
ParsedIntentInfoUtils、ParsedComponentUtils):观察 error 冒泡。 PackageParser2.parsePackage():确认返回值到异常的边界。ParallelPackageParser、InstallPackageHelper:确认 PMS 如何消费错误码。- split/cache 的 finally 和回退逻辑:理解失败后的资源清理与恢复。
11. 设计收束
Android 17 的包解析错误处理可以压缩为一条链:
解析函数
-> ParseInput 写入 success/error/deferred
-> ParseTypeImpl 持有线程内状态
-> 父函数逐层冒泡 ParseResult
-> target SDK 确定后触发 DeferredError
-> PackageParser2 转 PackageParserException
-> 扫描/安装调用方转 PackageManagerException 或安装失败这条链解释了几个关键现象:错误码和消息必须一起保存;success(null) 不是有效对象;deferred error 不是忽略错误,而是等待兼容性决策;缓存失败可以恢复而 Manifest 失败通常不能;资源关闭属于 parser/loader 的 finally,不属于错误对象本身。掌握这些边界,后续阅读安装事务和失败回滚时,才能准确判断一个失败究竟发生在解析、桥接、依赖校验还是提交阶段。
