Instant App 解析
本文承接 Manifest 解析入口、四大组件解析、IntentFilter 解析 和 Split APK 解析。这里讨论的是 PMS 在解析 APK 时如何记录 Instant App 相关状态,以及这些状态如何影响组件 filter、ApplicationInfo 和后续查询;不把安装 Instant App 的下载服务、cookie 持久化或 resolver 网络请求混入解析阶段。
Android 17 的实现不是一个名为 InstantAppParser 的独立模块。相关状态分散在 ApkLite、PackageImpl、ParsedActivityUtils、ParsedMainComponentUtils、PackageInfoUtils 和 ComponentResolver 中。读源码时必须把“Manifest 输入”“解析对象字段”“per-user 安装状态”“IntentFilter 可见性”分开,否则很容易把“应用是 instant app”和“filter 对 instant app 可见”误认为同一个布尔值。
1. 状态边界
1.1 解析主线
有三条不同的状态链:包级标志描述 APK 本身,组件级 filter visibility 描述某一条 Intent 规则是否允许 instant app 访问,PackageUserState.isInstantApp() 描述某个用户下的安装状态。它们最后在 PackageInfoUtils 和 resolver 查询阶段汇合。
1.2 本文范围
| 层 | 主要对象 | 本文要回答的问题 |
|---|---|---|
| 轻量 Manifest | ApkLite / PackageLite | split 与 package 的输入字段如何先被提取 |
| 完整包模型 | PackageImpl | 哪些字段代表包级 Instant 状态 |
| 组件模型 | ParsedActivity / ParsedMainComponent | visibleToInstantApps 和 filter visibility 如何派生 |
| 用户状态 | PackageUserState | 对外 ApplicationInfo 如何标记 instant app |
| 查询注册 | ComponentResolver | 解析后的 filter 何时影响 Intent 查询 |
2. 轻量输入
2.1 Manifest 标记
源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isolatedSplits", false);
boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isFeatureSplit", false);
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isSplitRequired", false);
String configForSplit = parser.getAttributeValue(null, "configForSplit");
String usesSplitName = null;
...
if (TAG_USES_SPLIT.equals(parser.getName())) {
if (usesSplitName != null) {
Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
continue;
}
usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
if (usesSplitName == null) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<uses-split> tag requires 'android:name' attribute");
}
}这些字段属于 split 组成和加载方式,不等于“当前包已经在某用户下作为 instant app 安装”。isFeatureSplit、isolatedSplits、configForSplit 和 uses-split 先进入 ApkLite,为后面的 PackageLite 与 AssetLoader 建立输入。
2.2 split 类型集合
public static ParseResult<Pair<Set<String>, Set<String>>> parseRequiredSplitTypes(
ParseInput input, XmlResourceParser parser) {
Set<String> requiredSplitTypes = null;
Set<String> splitTypes = null;
String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSplitTypes");
if (!TextUtils.isEmpty(value)) {
final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
if (result.isError()) {
return input.error(result);
}
requiredSplitTypes = result.getResult();
}
value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "splitTypes");
if (!TextUtils.isEmpty(value)) {
final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
if (result.isError()) {
return input.error(result);
}
splitTypes = result.getResult();
}
return input.success(Pair.create(requiredSplitTypes, splitTypes));
}Instant App 常与按需 feature split 一起出现,但 split type 校验本身属于 Split 解析规则。本文只记录它如何进入 package 状态,不把 required split type 当成 instant 标记。
3. 包级状态
3.1 PackageImpl 字段
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/pkg/PackageImpl.java
private static final int VISIBLE_TO_INSTANT_APPS = 0;
// Boolean storage is generated through the Booleans enum in the real class.
...
@Override
public boolean isVisibleToInstantApps() {
return getBoolean(Booleans.VISIBLE_TO_INSTANT_APPS);
}
@Override
public PackageImpl setVisibleToInstantApps(boolean value) {
return setBoolean(Booleans.VISIBLE_TO_INSTANT_APPS, value);
}Android 17 的 PackageImpl 使用生成的布尔槽位保存包级 visibleToInstantApps。它描述应用是否声明允许被 Instant App 看见,不表示该包自身一定是 instant app。后者来自用户维度的 PackageUserState。
3.2 用户状态标志
源码文件:frameworks/base/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
ai.flags |= flag(state.isStopped(), ApplicationInfo.FLAG_STOPPED)
| flag(state.isInstalled(), ApplicationInfo.FLAG_INSTALLED)
| flag(state.isSuspended(), ApplicationInfo.FLAG_SUSPENDED);
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);PRIVATE_FLAG_INSTANT 是从 state.isInstantApp() 派生的 per-user 输出。它在生成 ApplicationInfo 时才写入,因此不能通过静态解析对象判断某个用户当前是否已经把包作为 Instant App 安装。
4. Activity 标记
4.1 可见属性
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
boolean visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
if (visibleToEphemeral) {
activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
pkg.setVisibleToInstantApps(true);
}这里发生了两个状态写入:Activity 自身得到 FLAG_VISIBLE_TO_INSTANT_APP,同时 package 得到包级 visibleToInstantApps=true。后者是聚合状态,只要一个 Activity 声明可见就会被提升;代码没有在遇到后续 Activity 时自动降回 false。
4.2 Alias 限制
// TODO add visibleToInstantApps attribute to activity alias
final boolean visibleToEphemeral =
((activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0);
return parseActivityOrAlias(activity, pkg, tag, parser, res, sa,
false /*isReceiver*/, true /*isAlias*/, visibleToEphemeral, input,
R.styleable.AndroidManifestActivityAlias_parentActivityName,
R.styleable.AndroidManifestActivityAlias_permission,
R.styleable.AndroidManifestActivityAlias_exported);Android 17 源码明确留下 TODO:alias 没有独立的 visibleToInstantApps 属性读取,而是从已有 Activity flags 推导传入值。阅读 alias 的 Instant 行为时,不能套用普通 Activity 的 Manifest 属性模型。
5. Filter 可见性
5.1 公共解析入口
源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(
ParsedMainComponent mainComponent, ParsingPackage pkg, Resources resources,
XmlResourceParser parser, boolean visibleToEphemeral, boolean allowGlobs,
boolean allowAutoVerify, boolean allowImplicitEphemeralVisibility,
boolean failOnNoActions, ParseInput input) throws IOException, XmlPullParserException {
ParseResult<ParsedIntentInfoImpl> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
mainComponent.getName(), pkg, resources, parser, allowGlobs,
allowAutoVerify, input);
if (intentResult.isError()) {
return input.error(intentResult);
}
ParsedIntentInfo intent = intentResult.getResult();
IntentFilter intentFilter = intent.getIntentFilter();
if (intentFilter.countActions() == 0 && failOnNoActions) {
Slog.w(TAG, "No actions in " + parser.getName() + " at " + pkg.getBaseApkPath());
// Backward-compat, do not actually fail
return input.success(null);
}
final int visibility;
if (visibleToEphemeral) {
visibility = IntentFilter.VISIBILITY_EXPLICIT;
} else if (allowImplicitEphemeralVisibility
&& ComponentParseUtils.isImplicitlyExposedIntent(intent)) {
visibility = IntentFilter.VISIBILITY_IMPLICIT;
} else {
visibility = IntentFilter.VISIBILITY_NONE;
}
intentFilter.setVisibilityToInstantApp(visibility);
return input.success(intentResult.getResult());
}visibleToEphemeral 来自组件属性,allowImplicitEphemeralVisibility 来自调用方策略,二者共同决定单个 filter 的 visibility。即便 package 级 isVisibleToInstantApps() 为 true,也仍要看具体 filter 的 IntentFilter visibility。
5.2 Activity 调用方
private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
ParseInput input) throws IOException, XmlPullParserException {
ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(
activity, pkg, resources, parser, visibleToEphemeral,
true /*allowGlobs*/, true /*allowAutoVerify*/,
allowImplicitEphemeralVisibility, true /*failOnNoActions*/, input);
if (result.isError()) {
return input.error(result);
}
ParsedIntentInfoImpl intent = result.getResult();
if (intent != null) {
final IntentFilter filter = intent.getIntentFilter();
if (filter.isVisibleToInstantApp()) {
activity.setFlags(activity.getFlags()
| ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
}
if (filter.isImplicitlyVisibleToInstantApp()) {
activity.setFlags(activity.getFlags()
| ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
}
}
return input.success(intent);
}Activity/Receiver 会把 filter visibility 再投影到 Activity flags;Service/Provider 不执行这段 Activity 专属投影。这个二次派生让对外组件标志可以快速表达“至少有一条 filter 对 Instant App 可见”。
5.3 组件参数差异
| 调用方 | visibleToEphemeral | allowAutoVerify | allowImplicit... | failOnNoActions |
|---|---|---|---|---|
| Activity/Receiver | 来自 Activity 属性 | true | 普通 filter false,preferred true | true |
| Service | 来自 Service 属性 | false | false | false |
| Provider | 来自 Provider 属性 | false | false | false |
因此不能写成“所有组件的 Instant 可见性逻辑相同”。公共入口相同,但调用方传入的上下文不同,尤其是 Activity/Receiver 的 filter flags 和无 action 兼容分支。
6. 查询生效
6.1 Resolver 注册
源码文件:frameworks/base/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
public void addAllComponents(AndroidPackage pkg, boolean chatty,
@Nullable String setupWizardPackage, @NonNull Computer computer) {
synchronized (mLock) {
addActivitiesLocked(computer, pkg, newIntents, chatty);
addReceiversLocked(computer, pkg, chatty);
addProvidersLocked(computer, pkg, chatty);
addServicesLocked(computer, pkg, chatty);
onChanged();
}
}
protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
mActivities.put(a.getComponentName(), a);
for (int j = 0; j < a.getIntents().size(); j++) {
ParsedIntentInfo intent = a.getIntents().get(j);
addFilter(computer, Pair.create(a, intent));
}
}Instant 可见性在解析阶段写入 IntentFilter,但要等 addFilter() 把 pair 放进 resolver 索引后,查询才会真正看到它。包对象中有 filter 不等于全局 resolver 已注册。
6.2 查询过滤关系
查询还会叠加用户状态、包可见性和调用者 flags;IntentFilter visibility 只是其中一个输入。本文不把 MATCH_INSTANT 等查询常量伪装成解析字段,因为它们属于查询调用方。
7. PackageInfo 输出
7.1 Instant 标志
private static void initForUser(ApplicationInfo ai, PackageUserState state,
AndroidPackage pkg, int userId) {
ai.flags |= flag(state.isStopped(), ApplicationInfo.FLAG_STOPPED)
| flag(state.isInstalled(), ApplicationInfo.FLAG_INSTALLED);
ai.privateFlags |= flag(state.isInstantApp(),
ApplicationInfo.PRIVATE_FLAG_INSTANT);
}对外 ApplicationInfo 的 Instant 标记来自 PackageUserState,不是 PackageImpl.isVisibleToInstantApps()。一个普通安装包可以声明对 Instant App 可见;一个用户下的 Instant App 也可能有不同的 installed/stopped/hidden 状态。
7.2 Activity flags
if (intent != null) {
final IntentFilter intentFilter = intent.getIntentFilter();
if (intentFilter.isVisibleToInstantApp()) {
activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
}
if (intentFilter.isImplicitlyVisibleToInstantApp()) {
activity.setFlags(activity.getFlags()
| ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
}
}ActivityInfo 的可见 flags 可能由 Activity 自身属性先设置,也可能由某条 filter visibility 二次设置。两条来源最终都写入组件 flags,但不能据此反推 filter 数量或 package 的用户安装状态。
8. 状态机
8.1 三种可见状态
包级“可被 Instant 看见”和 filter 级 explicit/implicit visibility 是不同维度;只有 resolver 查询把二者与用户状态组合后,才形成最终结果。
8.2 结果汇合
| 状态来源 | 写入位置 | 生效时机 |
|---|---|---|
Activity visibleToInstantApps | Activity flags + package boolean | Activity 解析完成 |
| filter explicit/implicit | IntentFilter visibility | filter 解析完成 |
| 当前用户是 Instant App | ApplicationInfo.privateFlags | PackageInfoUtils 生成时 |
| resolver 注册 | IntentResolver 索引 | 扫描提交时 |
| 查询调用者 flags | query 参数 | 每次 Intent 查询 |
9. 错误与清理
9.1 解析错误
| 条件 | 结果 |
|---|---|
| 非法 split name | 轻量解析 error,包不进入完整解析 |
| 非法 split 依赖 | SplitAssetDependencyLoader 抛依赖错误 |
| Activity filter 缺 action | warning + success(null),该 filter 不加入列表 |
| filter 子标签错误 | 当前组件解析 error |
| class loader 名称非法 | 当前 split 解析 error |
| package 解析成功但用户不是 instant | 仍可成功,对外不设置 PRIVATE_FLAG_INSTANT |
Instant 本身通常不是一个“解析失败条件”。解析器记录声明和 filter visibility;用户是否以 instant 方式安装由安装状态决定,不会因为包级 boolean 为 false 就拒绝普通包。
9.2 清理路径
解析阶段没有独立的 Instant 注册表需要回滚。组件解析失败时,未提交的 ParsedPackage 不会进入 resolver;已经注册的 package 被替换或移除时,由 ComponentResolver.removeAllComponents() 对 intent pair 调用 removeFilter()。用户状态变化则由 PackageUserState 更新,不会修改 Manifest 解析对象中的静态声明。
10. 测试与诊断
10.1 输入与断言
| 输入 | 断言 | 证明范围 |
|---|---|---|
Activity visibleToInstantApps=true | Activity flag 和 package boolean 都为 true | 包/组件聚合 |
| 普通 Activity filter | IntentFilter visibility 为 NONE | 默认路径 |
| 显式可见 filter | visibility 为 EXPLICIT,Activity flag 同步 | filter 投影 |
| preferred implicit filter | visibility 为 IMPLICIT | 调用方策略 |
| Service/Provider filter | 不读取 autoVerify,不做 Activity flag 投影 | 组件差异 |
用户 isInstantApp=true | ApplicationInfo.PRIVATE_FLAG_INSTANT 设置 | per-user 输出 |
用户 isInstantApp=false | package 声明不影响 private flag | 静态/动态分离 |
| resolver 注册后移除 | 查询不再返回旧 filter | 生效/清理闭环 |
测试不能只检查 ApplicationInfo.privateFlags:那只能证明用户状态输出,不能证明 Manifest 声明、filter visibility 或 resolver 注册成功。
10.2 现场排查顺序
- 先区分问题是“包被当作 Instant App”还是“普通包对 Instant App 不可见”。
- 查看 Activity/Receiver 的
visibleToInstantApps属性是否设置 package 和 component flags。 - 检查具体 filter 的
IntentFilter.getVisibilityToInstantApp()。 - 确认组件解析调用方是否允许 implicit visibility,以及 filter 是否因无 action 返回 null。
- 查看
PackageInfoUtils生成时的PackageUserState.isInstantApp()。 - 最后确认
ComponentResolver.addFilter()是否已执行,以及查询 flags/user policy 是否过滤结果。
11. 源码路线
建议按以下顺序阅读:
ApkLiteParseUtils.parseApkLite():读取 split、feature 和 sandbox 相关轻量字段。PackageLite:确认 split 输入如何按索引保存。ParsingPackageUtils.parseClusterPackage():看 split loader 和完整 package 的汇合。ParsedActivityUtils.parseActivityOrReceiver():读取 Activity 可见属性并聚合 package 状态。ParsedMainComponentUtils.parseIntentFilter():计算 filter visibility。ParsedIntentInfoUtils.parseIntentInfo():确认 action/category/data 是 visibility 的输入。PackageInfoUtils:区分用户 Instant 标记与 package 声明。ComponentResolver.addAllComponents():确认 filter 何时进入查询索引。
12. 设计收束
Instant App 相关源码可以归纳为四段状态转换:
Manifest 属性
-> Package/Activity 静态声明
-> IntentFilter explicit/implicit visibility
-> PackageUserState 的 per-user instant 标志
-> Resolver 查询时组合并过滤最关键的边界是“声明”和“安装状态”分离:PackageImpl.isVisibleToInstantApps() 描述包级声明,IntentFilter visibility 描述单条规则,ApplicationInfo.PRIVATE_FLAG_INSTANT 描述当前用户的安装状态。只有把这三个 owner 和各自的生效时机串起来,才能解释为什么一个包可以被 Instant App 看见、为什么某条 filter 仍然不可见,以及为什么同一个 APK 在不同用户下的 ApplicationInfo flags 不同。
