Skip to content

Intent 匹配总览

理解 Intent 的显式分流、过滤器索引、跨 profile 合并与最终选择。

AndroidPMSPackageManagerIntentComponentResolver

Intent 匹配总览 ​

Intent 解析不是一个单独的 IntentFilter.match() 调用。Android 17 先在 ComputerEngine 中处理用户、调用者和显式组件,再由 ComponentResolver 进入按 type/scheme/action 建立的索引,最后由 ResolveIntentHelper 处理排序、首选项、浏览器和 ResolverActivity。

本文只讲总链路。具体的组件索引、匹配算法、queryIntentActivities、resolveActivity 和域名验证会在后续文章分别展开。

1. 解析分层 ​

层Android 17 真实类责任
入口ResolveIntentHelperresolve 单结果、首选项和 ResolverActivity
查询ComputerEngineflags、用户、显式/隐式分流、跨 profile
索引ComponentResolverActivity/Service/Receiver/Provider resolver
匹配IntentResolver候选桶、过滤器匹配、结果去重与排序
定义IntentFilteraction、data、category 的匹配分数

2. 解析入口 ​

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

java
public ResolveInfo resolveIntentInternal(
        Computer computer, Intent intent, String resolvedType,
        @PackageManager.ResolveInfoFlagsBits long flags,
        @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
        int userId, boolean resolveForStart,
        int filterCallingUid, int callingPid) {
    try {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
        if (!mUserManager.exists(userId)) return null;
        final int callingUid = Binder.getCallingUid();
        flags = computer.updateFlagsForResolve(
                flags, userId, filterCallingUid, resolveForStart,
                computer.isImplicitImageCaptureIntentAndNotSetByDpc(
                        intent, userId, resolvedType, flags));
        computer.enforceCrossUserPermission(
                callingUid, userId, false, false, "resolve intent");
        final List<ResolveInfo> query =
                computer.queryIntentActivitiesInternal(
                        intent, resolvedType, flags, privateResolveFlags,
                        filterCallingUid, callingPid, userId,
                        resolveForStart, true);

入口先验证用户存在,依据调用者和 resolve 场景调整 flags,再执行跨用户权限检查。它不自己遍历 resolver;查询职责下沉到 Computer,这样同一个快照查询逻辑可被多个 PMS API 复用。

java
        if (resolveForStart) {
            var args = new SaferIntentUtils.IntentArgs(
                    intent, resolvedType, false, true,
                    filterCallingUid, callingPid);
            args.platformCompat = mPlatformCompat;
            SaferIntentUtils.filterNonExportedComponents(args, query);
        }
        final boolean queryMayBeFiltered =
                UserHandle.getAppId(filterCallingUid)
                        >= Process.FIRST_APPLICATION_UID
                        && !resolveForStart;
        final ResolveInfo bestChoice = chooseBestActivity(
                computer, intent, resolvedType, flags,
                privateResolveFlags, query, userId, queryMayBeFiltered);
        if ((privateResolveFlags
                & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0
                && bestChoice != null && bestChoice.handleAllWebDataURI) {
            return null;
        }
        return bestChoice;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

resolveForStart 会额外过滤非 exported 组件;普通查询可能因为调用者可见性而被过滤。RESOLVE_NON_BROWSER_ONLY 又会拒绝最终选中的全 Web URI 浏览器。这些都是匹配成功之后的后置规则,不属于 IntentFilter.match。

3. 显式分流 ​

源码文件: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();
}

flags = updateFlagsForResolve(
        flags, userId, filterCallingUid, resolveForStart,
        comp != null || pkgName != null,
        isImplicitImageCaptureIntentAndNotSetByDpc(
                intent, userId, resolvedType, flags));

Selector 会暂时替换用于查询的 Intent,但保留 originalIntent,查询结束后还要验证最终组件是否匹配原 Intent。package 约束与 component 约束都影响显式分支判断;只有两者都没有时才进入全局隐式查询。

java
if (comp != null) {
    final ActivityInfo ai = getActivityInfo(comp, flags, userId);
    if (ai != null && !blockInstantResolution
            && !blockNormalResolution) {
        final ResolveInfo ri = new ResolveInfo();
        ri.activityInfo = ai;
        ri.userHandle = UserHandle.of(userId);
        list = new ArrayList<>(1);
        list.add(ri);
        SaferIntentUtils.enforceIntentFilterMatching(args, list);
    }
} else {
    QueryIntentActivitiesResult lockedResult =
            queryIntentActivitiesInternalBody(
                    intent, resolvedType, flags, filterCallingUid,
                    userId, resolveForStart, allowDynamicSplits,
                    pkgName, instantAppPkgName);
}

显式组件不会遍历所有 filters,但仍要检查 instant app 可见性、调用者与目标包关系,并由 SaferIntentUtils 验证过滤器约束。因此“显式 Intent 一定绕过所有安全检查”是错误的。

4. 隐式与 profile ​

源码文件: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);
}

隐式查询先处理当前 profile,再查询跨 profile 结果。SKIP_CURRENT_PROFILE 可能让当前 profile 完全不参与;当前结果中的系统用户过滤、instant app 允许条件和跨 profile 结果,都会影响最终列表。

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

最终返回前才合并 profile、域名偏好、instant app 结果和排序标记。ComponentResolver 的结果只是当前 profile 的候选,不是 API 最终可见结果。

5. Resolver 路由 ​

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

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

ComponentResolver 为 Activity、Service、Receiver、Provider 分别维护 resolver。Activity 查询进入 mActivities,其泛型对象保存解析后的组件和 ParsedIntentInfo,不会重新扫描 APK。

6. 候选索引桶 ​

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

java
String scheme = intent.getScheme();
F[] firstTypeCut = null;
F[] secondTypeCut = null;
F[] thirdTypeCut = null;
F[] schemeCut = null;

if (resolvedType != null) {
    int slashpos = resolvedType.indexOf('/');
    if (slashpos > 0) {
        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());
}

索引桶由 MIME type 和 URI scheme 决定:完整 type、基础 type、通配 type、scheme、typed action 和无 data action 各自对应不同索引。索引只是候选缩小,不代表已经匹配;所有候选仍要经过 IntentFilter.match。

7. 候选过滤顺序 ​

源码文件: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);
        }
    }
}

顺序很重要:停止应用、指定 package 和重复结果会在真正匹配前被过滤;match >= 0 后还要根据 defaultOnly 检查 CATEGORY_DEFAULT。因此一个 filter 的 action/data 都匹配,仍可能因为没有 DEFAULT category 而不进入 resolve 结果。

8. 最终选择 ​

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

java
if (query != null) {
    final int n = query.size();
    if (n == 1) return query.get(0);
    if (n > 1) {
        ResolveInfo r0 = query.get(0);
        ResolveInfo r1 = query.get(1);
        if (r0.priority != r1.priority
                || r0.preferredOrder != r1.preferredOrder
                || r0.isDefault != r1.isDefault) {
            return query.get(0);
        }
        ResolveInfo preferred =
                mPreferredActivityHelper.findPreferredActivityNotLocked(
                        computer, intent, resolvedType, flags, query,
                        true, false, debug, userId, queryMayBeFiltered);
        if (preferred != null) return preferred;
        // 构造 ResolverActivity 的 ResolveInfo
    }
}
return null;

单结果直接返回;多个结果先比较 priority、preferredOrder 和 isDefault。三者都相同才查持久化 preferred activity,仍无法唯一选择时构造 ResolverActivity 的 ResolveInfo。ResolverActivity 不是某个业务应用的匹配结果,而是系统为“让用户选择”生成的虚拟结果。

9. 失败定位清单 ​

  1. 显式 Intent:确认 ComponentName 对应组件存在、用户存在、exported/instant 可见性通过。
  2. 隐式 Intent:确认 action、MIME type、scheme、categories 与 filter 的候选桶和匹配参数一致。
  3. 只有查询结果没有 resolve 结果:查看 priority、preferred activity 和 RESOLVE_NON_RESOLVER_ONLY。
  4. 当前用户没有结果:检查 SKIP_CURRENT_PROFILE、跨 profile filter、package visibility 和用户状态。
  5. filter 明明匹配却被丢弃:检查 stopped、package 限制、CATEGORY_DEFAULT 和 post-resolution filter。
  6. Web Intent 结果异常:继续查看 domain verification、browser 计数和 instant app 结果合并。

10. 源码阅读路线 ​

  1. ResolveIntentHelper.resolveIntentInternal:入口、flags、后置过滤和最终选择。
  2. ComputerEngine.queryIntentActivitiesInternal:selector、显式/隐式分流和安全过滤。
  3. queryIntentActivitiesInternalBody:当前 profile、跨 profile、instant app 和域名合并。
  4. ComponentResolver.queryActivities:组件类型到 resolver 的路由。
  5. IntentResolver.queryIntent:候选索引桶和查询顺序。
  6. IntentResolver.buildResolveList:停止包、package、去重、DEFAULT category 与匹配结果。

Intent 解析的核心不是“遍历所有组件”,而是快照查询、候选索引、过滤器匹配、profile 合并和最终选择组成的流水线。任何一个阶段都可能减少结果,因此排查时必须沿这条顺序读取,而不能只检查 manifest 中是否写了一个 <intent-filter>。