Skip to content

隐式 Intent 解析

拆解隐式 Intent 的候选索引、过滤器匹配、去重与跨 profile 合并。

AndroidPMSPackageManagerIntent

隐式 Intent 解析 ​

隐式 Intent 解析的输入没有固定 ComponentName,系统必须从已注册的 IntentFilter 中找到候选 Activity。Android 17 的真实路径是:ComputerEngine 决定查询范围,ComponentResolver 路由到 Activity resolver,IntentResolver 选择索引桶并逐个调用 IntentFilter.match,最后再合并 profile、Instant App 和动态 split 结果。

1. 一次查询的阶段 ​

阶段主要类输出
范围确定ComputerEngine当前/指定 package/profile
候选索引IntentResolvertype/scheme/action cut
精确匹配IntentFiltermatch 分数或失败码
结果生成ActivityIntentResolverResolveInfo
后处理CrossProfileIntentResolverEngine / ComputerEngine最终候选列表

2. 隐式分流入口 ​

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

java
final String pkgName = intent.getPackage();
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null && intent.getSelector() != null) {
    originalIntent = intent;
    intent = intent.getSelector();
    comp = intent.getComponent();
}

if (comp != null) {
    // 显式路径:getActivityInfo
} else {
    QueryIntentActivitiesResult lockedResult =
            queryIntentActivitiesInternalBody(
                    intent, resolvedType, flags, filterCallingUid,
                    userId, resolveForStart, allowDynamicSplits,
                    pkgName, instantAppPkgName);
}

只有没有 component 的 Intent 才进入本文路径。selector 会暂时替代原 Intent,但查询结束后仍会用 originalIntent 做安全匹配验证。

3. 全局隐式查询 ​

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

java
if (pkgName == null) {
    if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(
            this, intent, resolvedType, userId)) {
        final List<ResolveInfo> queryResult =
                mComponentResolver.queryActivities(
                        this, intent, resolvedType, flags, userId);
        if (queryResult != null) {
            result.addAll(filterIfNotSystemUser(queryResult, userId));
        }
    }
    addInstant = isInstantAppResolutionAllowed(
            intent, result, userId, false, flags);
    boolean hasNonNegativePriorityResult =
            hasNonNegativePriority(result);
    crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(
            this, intent, resolvedType, userId, flags, pkgName,
            hasNonNegativePriorityResult, resolveForStart,
            mSettings::getPackage);
    if (intent.hasWebURI() || !crossProfileResults.isEmpty()) {
        sortResult = true;
    }
}

当前 profile 可能被 CrossProfile filter 的 SKIP_CURRENT_PROFILE 跳过;否则先由 ComponentResolver 查询本 profile。只有当前结果没有非负 priority 候选时,Instant App 解析更可能被加入。Web URI 或跨 profile 结果存在时,最终需要重新排序。

4. 包内查询 ​

java
} else {
    final PackageStateInternal setting =
            getPackageStateInternal(pkgName, Process.SYSTEM_UID);
    if (setting != null && setting.getAndroidPackage() != null
            && (resolveForStart
            || !shouldFilterApplication(
                    setting, filterCallingUid, userId))) {
        final List<ResolveInfo> queryResult =
                mComponentResolver.queryActivities(
                        this, intent, resolvedType, flags,
                        setting.getAndroidPackage().getActivities(),
                        userId);
        if (queryResult != null) {
            result.addAll(filterIfNotSystemUser(queryResult, userId));
        }
    }
    if (result.isEmpty()) {
        addInstant = isInstantAppResolutionAllowed(
                intent, null, userId, true, flags);
    }
    crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(
            this, intent, resolvedType, userId, flags, pkgName,
            false, resolveForStart, mSettings::getPackage);
}

指定 package 时只在该包的 Activity 列表中匹配,不扫描全局索引。包不可见时普通查询返回空;resolveForStart 会改变这一步的过滤语义。指定 package 并不阻止跨 profile 查询,但跨 profile 结果不会因为当前结果 priority 非负而走全局同样的条件。

5. Resolver 桥接 ​

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

java
@Override
public List<ResolveInfo> queryActivities(
        Computer computer, Intent intent, String resolvedType,
        long flags, int userId) {
    return mActivities.queryIntent(
            computer, intent, resolvedType, flags, userId);
}

@Override
public List<ResolveInfo> queryActivities(
        Computer computer, Intent intent, String resolvedType,
        long flags, List<ParsedActivity> activities, int userId) {
    return mActivities.queryIntentForPackage(
            computer, intent, resolvedType, flags, activities, userId);
}

全局分支进入全量 Activity resolver,package 分支进入限定列表查询。两者共享底层匹配逻辑,但候选集合不同。

6. 候选 cut ​

源码文件:frameworks/base/services/core/java/com/android/server/IntentResolver.java

java
F[] firstTypeCut = null;
F[] secondTypeCut = null;
F[] thirdTypeCut = null;
F[] schemeCut = null;

if (resolvedType != null) {
    int slashpos = resolvedType.indexOf('/');
    if (slashpos > 0) {
        final String baseType = resolvedType.substring(0, slashpos);
        if (!baseType.equals("*")) {
            if (resolvedType.length() != slashpos + 2
                    || resolvedType.charAt(slashpos + 1) != '*') {
                firstTypeCut = mTypeToFilter.get(resolvedType);
                secondTypeCut = mWildTypeToFilter.get(baseType);
            } else {
                firstTypeCut = mBaseTypeToFilter.get(baseType);
                secondTypeCut = mWildTypeToFilter.get(baseType);
            }
            thirdTypeCut = mWildTypeToFilter.get("*");
        } else if (intent.getAction() != null) {
            firstTypeCut = mTypedActionToFilter.get(intent.getAction());
        }
    }
}
if (scheme != null) {
    schemeCut = mSchemeToFilter.get(scheme);
}
if (resolvedType == null && scheme == null
        && intent.getAction() != null) {
    firstTypeCut = mActionToFilter.get(intent.getAction());
}

四类 cut 分别覆盖精确 MIME、基础 MIME、通配 MIME 和 scheme;没有 type/scheme 时使用 action cut。cut 只是缩小候选,不是最终匹配。

7. 四路执行与排序 ​

java
if (firstTypeCut != null) {
    buildResolveList(computer, intent, categories, debug,
            defaultOnly, resolvedType, scheme,
            firstTypeCut, finalList, userId, customFlags);
}
if (secondTypeCut != null) {
    buildResolveList(computer, intent, categories, debug,
            defaultOnly, resolvedType, scheme,
            secondTypeCut, finalList, userId, customFlags);
}
if (thirdTypeCut != null) {
    buildResolveList(computer, intent, categories, debug,
            defaultOnly, resolvedType, scheme,
            thirdTypeCut, finalList, userId, customFlags);
}
if (schemeCut != null) {
    buildResolveList(computer, intent, categories, debug,
            defaultOnly, resolvedType, scheme,
            schemeCut, finalList, userId, customFlags);
}
filterResults(finalList);
sortResults(finalList);
return finalList;

不同 cut 的结果先进入同一 finalList,再统一去重/过滤/排序。不能把每个 cut 的局部第一项当成全局第一项。

8. Filter 过滤顺序 ​

源码文件:frameworks/base/services/core/java/com/android/server/IntentResolver.java

java
for (int i = 0; i < N && (filter = src[i]) != null; i++) {
    if (excludingStopped && isFilterStopped(computer, filter, userId)) {
        continue;
    }
    if (packageName != null
            && !isPackageForFilter(packageName, filter)) {
        continue;
    }
    if (!allowFilterResult(filter, dest)) {
        continue;
    }
    match = intentFilter.match(
            action, resolvedType, scheme, data, categories, TAG);
    if (match >= 0) {
        if (!defaultOnly
                || intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) {
            final R oneResult = newResult(
                    computer, filter, match, userId, customFlags);
            if (oneResult != null) dest.add(oneResult);
        } else {
            hasNonDefaults = true;
        }
    }
}

顺序是 stopped → package → 重复结果 → IntentFilter.match → DEFAULT → newResult。所以 filter 匹配成功并不保证进入列表:没有 DEFAULT、组件不可用或用户状态不匹配都可能在后续被丢弃。

9. Profile 合并 ​

java
return mCrossProfileIntentResolverEngine
        .combineFilterAndCreateQueryActivitiesResponse(
                this, intent, resolvedType, instantAppPkgName,
                pkgName, allowDynamicSplits, flags, userId,
                filterCallingUid, resolveForStart, result,
                crossProfileResults,
                areWebInstantAppsDisabled(userId), addInstant,
                sortResult, mSettings::getPackage);

当前 profile 的 resolver 结果只是中间列表。合并阶段还会处理关联 profile、域名偏好、Web Instant App 开关、动态 split 和最终排序标志。

10. 排查顺序 ​

  1. 确认 Intent 没有 component,且 selector 替换后的 Intent 符合预期。
  2. 检查 pkgName:指定 package 时不会查询全局 Activity resolver。
  3. 根据 type/scheme/action 判断使用了哪个 cut;开启 FLAG_DEBUG_LOG_RESOLUTION 查看候选桶和匹配日志。
  4. 检查 stopped、package filter、重复组件和 MATCH_DEFAULT_ONLY。
  5. 对当前用户无结果的问题,检查 SKIP_CURRENT_PROFILE、system user 过滤和 CrossProfileIntentResolverEngine。
  6. 对候选存在但最终缺失的问题,检查 Instant App、Web 开关、动态 split 和 post-resolution filter。

11. 源码阅读路线 ​

  1. ComputerEngine.queryIntentActivitiesInternal:隐式分流和后处理。
  2. queryIntentActivitiesInternalBody:当前 profile、指定 package 与跨 profile。
  3. ComponentResolverBase.queryActivities:全局与包内 resolver 路由。
  4. IntentResolver.queryIntent:候选 cut 选择和四路执行。
  5. IntentResolver.buildResolveList:逐 filter 过滤和结果生成。
  6. IntentFilter.match:action/data/category 的底层判定。

隐式 Intent 解析不是一次全量遍历,而是“范围裁剪 → 候选索引 → 逐 filter 匹配 → 组件状态过滤 → profile/Instant 合并”的流水线。排查时必须沿这个顺序判断结果在哪一层消失。