Intent 匹配总览
Intent 解析不是一个单独的 IntentFilter.match() 调用。Android 17 先在 ComputerEngine 中处理用户、调用者和显式组件,再由 ComponentResolver 进入按 type/scheme/action 建立的索引,最后由 ResolveIntentHelper 处理排序、首选项、浏览器和 ResolverActivity。
本文只讲总链路。具体的组件索引、匹配算法、queryIntentActivities、resolveActivity 和域名验证会在后续文章分别展开。
1. 解析分层
| 层 | Android 17 真实类 | 责任 |
|---|---|---|
| 入口 | ResolveIntentHelper | resolve 单结果、首选项和 ResolverActivity |
| 查询 | ComputerEngine | flags、用户、显式/隐式分流、跨 profile |
| 索引 | ComponentResolver | Activity/Service/Receiver/Provider resolver |
| 匹配 | IntentResolver | 候选桶、过滤器匹配、结果去重与排序 |
| 定义 | IntentFilter | action、data、category 的匹配分数 |
2. 解析入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.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 复用。
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
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 约束都影响显式分支判断;只有两者都没有时才进入全局隐式查询。
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
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 结果,都会影响最终列表。
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
@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
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
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
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. 失败定位清单
- 显式 Intent:确认
ComponentName对应组件存在、用户存在、exported/instant 可见性通过。 - 隐式 Intent:确认 action、MIME type、scheme、categories 与 filter 的候选桶和匹配参数一致。
- 只有查询结果没有 resolve 结果:查看 priority、preferred activity 和
RESOLVE_NON_RESOLVER_ONLY。 - 当前用户没有结果:检查
SKIP_CURRENT_PROFILE、跨 profile filter、package visibility 和用户状态。 - filter 明明匹配却被丢弃:检查 stopped、package 限制、
CATEGORY_DEFAULT和 post-resolution filter。 - Web Intent 结果异常:继续查看 domain verification、browser 计数和 instant app 结果合并。
10. 源码阅读路线
ResolveIntentHelper.resolveIntentInternal:入口、flags、后置过滤和最终选择。ComputerEngine.queryIntentActivitiesInternal:selector、显式/隐式分流和安全过滤。queryIntentActivitiesInternalBody:当前 profile、跨 profile、instant app 和域名合并。ComponentResolver.queryActivities:组件类型到 resolver 的路由。IntentResolver.queryIntent:候选索引桶和查询顺序。IntentResolver.buildResolveList:停止包、package、去重、DEFAULT category 与匹配结果。
Intent 解析的核心不是“遍历所有组件”,而是快照查询、候选索引、过滤器匹配、profile 合并和最终选择组成的流水线。任何一个阶段都可能减少结果,因此排查时必须沿这条顺序读取,而不能只检查 manifest 中是否写了一个 <intent-filter>。
