隐式 Intent 解析
隐式 Intent 解析的输入没有固定 ComponentName,系统必须从已注册的 IntentFilter 中找到候选 Activity。Android 17 的真实路径是:ComputerEngine 决定查询范围,ComponentResolver 路由到 Activity resolver,IntentResolver 选择索引桶并逐个调用 IntentFilter.match,最后再合并 profile、Instant App 和动态 split 结果。
1. 一次查询的阶段
| 阶段 | 主要类 | 输出 |
|---|---|---|
| 范围确定 | ComputerEngine | 当前/指定 package/profile |
| 候选索引 | IntentResolver | type/scheme/action cut |
| 精确匹配 | IntentFilter | match 分数或失败码 |
| 结果生成 | ActivityIntentResolver | ResolveInfo |
| 后处理 | CrossProfileIntentResolverEngine / ComputerEngine | 最终候选列表 |
2. 隐式分流入口
源码文件: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();
}
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
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. 包内查询
} 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
@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
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. 四路执行与排序
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
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 合并
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. 排查顺序
- 确认 Intent 没有 component,且 selector 替换后的 Intent 符合预期。
- 检查
pkgName:指定 package 时不会查询全局 Activity resolver。 - 根据 type/scheme/action 判断使用了哪个 cut;开启
FLAG_DEBUG_LOG_RESOLUTION查看候选桶和匹配日志。 - 检查 stopped、package filter、重复组件和
MATCH_DEFAULT_ONLY。 - 对当前用户无结果的问题,检查
SKIP_CURRENT_PROFILE、system user 过滤和 CrossProfileIntentResolverEngine。 - 对候选存在但最终缺失的问题,检查 Instant App、Web 开关、动态 split 和 post-resolution filter。
11. 源码阅读路线
ComputerEngine.queryIntentActivitiesInternal:隐式分流和后处理。queryIntentActivitiesInternalBody:当前 profile、指定 package 与跨 profile。ComponentResolverBase.queryActivities:全局与包内 resolver 路由。IntentResolver.queryIntent:候选 cut 选择和四路执行。IntentResolver.buildResolveList:逐 filter 过滤和结果生成。IntentFilter.match:action/data/category 的底层判定。
隐式 Intent 解析不是一次全量遍历,而是“范围裁剪 → 候选索引 → 逐 filter 匹配 → 组件状态过滤 → profile/Instant 合并”的流水线。排查时必须沿这个顺序判断结果在哪一层消失。
