Skip to content

Instant App 解析

追踪 Instant App 的 Manifest 标记、组件可见性、sandbox 字段和查询状态如何进入 Android 17 包模型。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageParserInstant App源码阅读

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 本文范围 ​

层主要对象本文要回答的问题
轻量 ManifestApkLite / PackageLitesplit 与 package 的输入字段如何先被提取
完整包模型PackageImpl哪些字段代表包级 Instant 状态
组件模型ParsedActivity / ParsedMainComponentvisibleToInstantApps 和 filter visibility 如何派生
用户状态PackageUserState对外 ApplicationInfo 如何标记 instant app
查询注册ComponentResolver解析后的 filter 何时影响 Intent 查询

2. 轻量输入 ​

2.1 Manifest 标记 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

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 类型集合 ​

java
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

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

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

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 限制 ​

java
// 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

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 调用方 ​

java
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 组件参数差异 ​

调用方visibleToEphemeralallowAutoVerifyallowImplicit...failOnNoActions
Activity/Receiver来自 Activity 属性true普通 filter false,preferred truetrue
Service来自 Service 属性falsefalsefalse
Provider来自 Provider 属性falsefalsefalse

因此不能写成“所有组件的 Instant 可见性逻辑相同”。公共入口相同,但调用方传入的上下文不同,尤其是 Activity/Receiver 的 filter flags 和无 action 兼容分支。

6. 查询生效 ​

6.1 Resolver 注册 ​

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

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 标志 ​

java
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 ​

java
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 visibleToInstantAppsActivity flags + package booleanActivity 解析完成
filter explicit/implicitIntentFilter visibilityfilter 解析完成
当前用户是 Instant AppApplicationInfo.privateFlagsPackageInfoUtils 生成时
resolver 注册IntentResolver 索引扫描提交时
查询调用者 flagsquery 参数每次 Intent 查询

9. 错误与清理 ​

9.1 解析错误 ​

条件结果
非法 split name轻量解析 error,包不进入完整解析
非法 split 依赖SplitAssetDependencyLoader 抛依赖错误
Activity filter 缺 actionwarning + 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=trueActivity flag 和 package boolean 都为 true包/组件聚合
普通 Activity filterIntentFilter visibility 为 NONE默认路径
显式可见 filtervisibility 为 EXPLICIT,Activity flag 同步filter 投影
preferred implicit filtervisibility 为 IMPLICIT调用方策略
Service/Provider filter不读取 autoVerify,不做 Activity flag 投影组件差异
用户 isInstantApp=trueApplicationInfo.PRIVATE_FLAG_INSTANT 设置per-user 输出
用户 isInstantApp=falsepackage 声明不影响 private flag静态/动态分离
resolver 注册后移除查询不再返回旧 filter生效/清理闭环

测试不能只检查 ApplicationInfo.privateFlags:那只能证明用户状态输出,不能证明 Manifest 声明、filter visibility 或 resolver 注册成功。

10.2 现场排查顺序 ​

  1. 先区分问题是“包被当作 Instant App”还是“普通包对 Instant App 不可见”。
  2. 查看 Activity/Receiver 的 visibleToInstantApps 属性是否设置 package 和 component flags。
  3. 检查具体 filter 的 IntentFilter.getVisibilityToInstantApp()。
  4. 确认组件解析调用方是否允许 implicit visibility,以及 filter 是否因无 action 返回 null。
  5. 查看 PackageInfoUtils 生成时的 PackageUserState.isInstantApp()。
  6. 最后确认 ComponentResolver.addFilter() 是否已执行,以及查询 flags/user policy 是否过滤结果。

11. 源码路线 ​

建议按以下顺序阅读:

  1. ApkLiteParseUtils.parseApkLite():读取 split、feature 和 sandbox 相关轻量字段。
  2. PackageLite:确认 split 输入如何按索引保存。
  3. ParsingPackageUtils.parseClusterPackage():看 split loader 和完整 package 的汇合。
  4. ParsedActivityUtils.parseActivityOrReceiver():读取 Activity 可见属性并聚合 package 状态。
  5. ParsedMainComponentUtils.parseIntentFilter():计算 filter visibility。
  6. ParsedIntentInfoUtils.parseIntentInfo():确认 action/category/data 是 visibility 的输入。
  7. PackageInfoUtils:区分用户 Instant 标记与 package 声明。
  8. ComponentResolver.addAllComponents():确认 filter 何时进入查询索引。

12. 设计收束 ​

Instant App 相关源码可以归纳为四段状态转换:

text
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 不同。