queryIntentActivities
queryIntentActivities 返回的是“当前调用者可见、当前用户可用、匹配过滤器且经过后处理”的 Activity 列表,而不是所有注册了相同 action 的组件。Android 17 的查询链会处理 selector、显式组件、指定 package、跨 profile、Instant App、动态 split 和包可见性。
1. API 契约
源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java
@NonNull
public abstract List<ResolveInfo> queryIntentActivities(
@NonNull Intent intent, int flags);
@NonNull
public abstract List<ResolveInfo> queryIntentActivitiesAsUser(
@NonNull Intent intent, int flags, @UserIdInt int userId);返回列表按 best-to-worst 排序,列表第一项就是 resolveActivity 通常会选择的候选。MATCH_DEFAULT_ONLY 限制过滤器必须声明 CATEGORY_DEFAULT,MATCH_ALL 则改变默认过滤策略。查询 API 返回列表,即使没有结果也返回空列表而不是 null。
2. PMS 到 Computer
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public ParceledListSlice<ResolveInfo> queryIntentActivities(
Intent intent, String resolvedType, long flags, int userId) {
final Computer computer = snapshotComputer();
final List<ResolveInfo> result =
computer.queryIntentActivitiesInternal(
intent, resolvedType, flags, userId);
return new ParceledListSlice<>(result);
}PMS 对外入口先取得 Computer 快照,再把查询交给 ComputerEngine。快照让本次查询看到一致的包和组件状态;Binder 返回层只负责把结果包装成 ParceledListSlice。
3. 主入口前置处理
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(
Intent intent, String resolvedType, long flags,
long privateResolveFlags, int filterCallingUid,
int callingPid, int userId, boolean resolveForStart,
boolean allowDynamicSplits) {
if (!mUserManager.exists(userId)) {
return Collections.emptyList();
}
flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
final String instantAppPkgName =
getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(
Binder.getCallingUid(), userId, false, false,
"query intent activities");用户不存在时直接返回空列表。Android 17 还强制加入 MATCH_QUARANTINED_COMPONENTS,并用真实 Binder caller 做跨用户检查;filterCallingUid 则用于结果可见性。
4. selector 与显式
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 被保留,查询结束后还要验证结果是否满足原 Intent。component 和 package 都属于“显式约束”,会影响 flags 修正和后续查询分支。
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);
}
}显式组件不遍历所有 filters,但仍检查 instant app 可见性、包可见性和安全 filter matching;不存在或被阻止时返回空列表。
5. 全局隐式查询
源码文件: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 查询可能被 SKIP_CURRENT_PROFILE 跳过;结果还会经过 system user 过滤。只有当前结果没有非负 priority 候选时,Instant App 解析才更可能被加入。随后 CrossProfileIntentResolverEngine 查询关联 profile。
6. 包内查询
} 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);
}
}指定 package 时只在该包的 Activity 列表中匹配,不扫描全局 resolver。包不可见时普通查询为空,但 resolveForStart 有不同的过滤语义;指定 package 仍可能尝试 Instant App 结果。
7. 跨 profile 合并
return mCrossProfileIntentResolverEngine
.combineFilterAndCreateQueryActivitiesResponse(
this, intent, resolvedType, instantAppPkgName,
pkgName, allowDynamicSplits, flags, userId,
filterCallingUid, resolveForStart, result,
crossProfileResults,
areWebInstantAppsDisabled(userId), addInstant,
sortResult, mSettings::getPackage);合并函数负责把当前 profile、关联 profile、Instant App 和域名偏好组合成最终候选。sortResult 在 Web URI 或存在跨 profile 结果时开启,不能假设 ComponentResolver 返回的局部顺序就是最终顺序。
8. 后置过滤
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final List<ResolveInfo> applyPostResolutionFilter(
List<ResolveInfo> resolveInfos, String ephemeralPkgName,
boolean allowDynamicSplits, int filterCallingUid,
boolean resolveForStart, int userId, Intent intent) {
final boolean blockInstant = intent.isWebIntent()
&& areWebInstantAppsDisabled(userId);
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
final ResolveInfo info = resolveInfos.get(i);
if (info.isInstantAppAvailable && blockInstant) {
resolveInfos.remove(i);
continue;
}
if (allowDynamicSplits && info.activityInfo != null
&& info.activityInfo.splitName != null
&& !ArrayUtils.contains(
info.activityInfo.applicationInfo.splitNames,
info.activityInfo.splitName)) {
if (instantAppInstallerActivity() == null) {
resolveInfos.remove(i);
continue;
}
// 用 Instant App installer 替换尚未安装的 split 结果
}
}
return resolveInfos;
}Web Instant App 被禁用时先移除对应结果;缺少动态 split 时,如果有 installer,就把候选替换为安装器 ResolveInfo,否则移除。这个后处理发生在 filter match 之后,所以“匹配成功但列表没有原 Activity”可能是动态 split 或 Web Instant App 规则造成的。
9. 结果进入列表
底层 IntentResolver 按 action/type/scheme 选择候选桶,并在 buildResolveList 中调用 IntentFilter.match。匹配成功后由 Activity resolver 生成 ResolveInfo;MATCH_DEFAULT_ONLY 还要求 filter 拥有 CATEGORY_DEFAULT。同一 Activity 的多个 filter 会通过 allowFilterResult 去重。
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);
}
}queryIntentActivities 的排序输入来自 ResolveInfo.priority、preferredOrder、isDefault、match 和 system 属性。PMS 的最终列表还可能因跨 profile、域名验证和 package visibility 改变。
10. 排查清单
- 先确认目标 user 存在,且调用者有跨用户查询权限。
- 检查 Intent 是否带 selector、component 或 package;它们决定查询分支。
- 查看 action/type/scheme/category 是否进入正确 resolver 候选桶。
- 检查
MATCH_DEFAULT_ONLY是否丢弃没有 DEFAULT category 的 filter。 - 检查 stopped、包可见性、system user 和 instant app 过滤。
- 对工作资料场景检查 CrossProfileIntentResolverEngine 是否跳过当前 profile。
- 对 Web/动态 feature 场景检查 domain approval、Web Instant App 开关和 split installer。
- 需要解释顺序时,比较 ResolveInfo 的 priority、preferredOrder、isDefault 和 match,而不是只看包名。
11. 源码阅读路线
PackageManager的queryIntentActivities契约。PackageManagerService的 Binder 转发和snapshotComputer()。ComputerEngine.queryIntentActivitiesInternal:前置 flags、selector 和显式分流。queryIntentActivitiesInternalBody:当前 profile、指定 package 与跨 profile。applyPostResolutionFilter:Instant App、动态 split 和最终可见性。ComponentResolver、IntentResolver和IntentFilter.match:候选生成与底层匹配。
queryIntentActivities 的结果是多个阶段共同收敛的产物:注册索引只负责产生候选,Computer 负责用户和包范围,后置过滤负责可见性与动态安装,最终列表才具有调用方可以观察到的语义。
