resolveActivity
queryIntentActivities 返回候选列表,resolveActivity 则要把列表收敛成一个 ResolveInfo。Android 17 的单选逻辑并不只是取第一项:它先依赖查询阶段排序,再比较 priority/preferredOrder/isDefault,随后查用户首选项、Instant App 域名批准,最后才构造 ResolverActivity 或返回 null。
1. 查询与单选的边界
| 阶段 | 方法 | 结果 |
|---|---|---|
| 候选生成 | Computer.queryIntentActivitiesInternal | List<ResolveInfo> |
| 单选编排 | ResolveIntentHelper.resolveIntentInternal | 一个 ResolveInfo 或 null |
| 歧义消解 | chooseBestActivity | 目标 Activity、ResolverActivity 或 null |
2. 解析入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
public ResolveInfo resolveIntentInternal(
Computer computer, Intent intent, String resolvedType,
long flags, 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);用户检查、flags 修正和跨用户检查发生在查询之前。callingUid 是真实 Binder 身份,filterCallingUid 参与包可见性;二者分离是系统服务代查场景的重要安全边界。
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);
}
}启动场景会移除非 exported 组件;最终结果若是通用浏览器且设置 RESOLVE_NON_BROWSER_ONLY,也会被拒绝。两者都发生在候选查询之后。
3. 初始决策
源码文件: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);
} else 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);
}零结果(包括空列表)返回 null;一个结果直接返回。多个结果只要前两项在三个字段中存在差异,排序后的第一项就被视为明确赢家。match 已参与候选排序,但不会在这里单独触发新的比较分支。
4. 用户首选
ResolveInfo ri = mPreferredActivityHelper.findPreferredActivityNotLocked(
computer, intent, resolvedType, flags, query,
true /* always */, false /* removeMatches */,
debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}只有前三项相同、系统无法自动分出优先级时才查询用户保存的 preferred activity。removeMatches=false 表示这里只读偏好,不修改候选集合;queryMayBeFiltered 防止基于不完整可见列表错误命中偏好。
5. Instant 与浏览器
int browserCount = 0;
for (int i = 0; i < n; i++) {
ri = query.get(i);
if (ri.handleAllWebDataURI) browserCount++;
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final PackageStateInternal ps =
computer.getPackageStateInternal(ri.activityInfo.packageName);
if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
mDomainVerificationManager, ps, intent, flags, userId)) {
return ri;
}
}
}通过域名批准的 Instant App 可以越过 ResolverActivity 直接成为结果。browserCount 同时统计通用 Web handler,供后面设置 ResolverActivity 的 handleAllWebDataURI。
6. ResolverActivity
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
if ((privateResolveFlags
& PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
return null;
}
ri = new ResolveInfo(mResolveInfoSupplier.get());
ri.handleAllWebDataURI = browserCount == n;
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
if (ri.userHandle == null) ri.userHandle = UserHandle.of(userId);RESOLVE_NON_RESOLVER_ONLY 是内部调用的“不要选择器”约束。未设置时,supplier 提供系统 ResolverActivity 模板,helper 复制 ActivityInfo 后填充 action 对应标签和目标用户。
final String intentPackage = intent.getPackage();
if (!TextUtils.isEmpty(intentPackage)
&& allHavePackage(query, intentPackage)) {
final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
ri.resolvePackageName = intentPackage;
if (mUserNeedsBadging.get(userId)) {
ri.noResourceId = true;
} else {
ri.icon = appi.icon;
}
ri.iconResourceId = appi.icon;
ri.labelRes = appi.labelRes;
}
ri.activityInfo.applicationInfo =
new ApplicationInfo(ri.activityInfo.applicationInfo);
if (userId != 0) {
ri.activityInfo.applicationInfo.uid = UserHandle.getUid(
userId, UserHandle.getAppId(ri.activityInfo.getUid()));
}
if (ri.activityInfo.metaData == null) {
ri.activityInfo.metaData = new Bundle();
}
ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
return ri;指定 package 且所有候选来自同一包时,选择器借用该包的 label/icon;需要 badging 时不直接返回资源 ID。非主用户会重写 UID,METADATA_DOCK_HOME 保证车载/dock 场景可展示。
7. 内部调用示例
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
final ResolveInfo resolveInfo = mResolveIntentHelper.resolveIntentInternal(
computer, intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE
| MATCH_DIRECT_BOOT_UNAWARE,
0, UserHandle.USER_SYSTEM, false,
Binder.getCallingUid(), Binder.getCallingPid());
if (resolveInfo == null ||
mResolveActivity.name.equals(resolveInfo.getComponentInfo().name)) {
throw new RuntimeException(
"There must be exactly one uninstaller; found " + resolveInfo);
}系统启动时查找 uninstaller 就依赖这条单选链,并明确拒绝“无结果”或“结果仍是通用 ResolveActivity”。这说明 resolveActivity 的返回值会被系统服务当作唯一能力提供者,而不只是给 UI 展示。
8. 排查顺序
- 先看 query 是否为空,再看 resolve 是否因
NON_RESOLVER_ONLY返回 null。 - 多候选时比较 priority、preferredOrder、isDefault,确认是否进入 preferred 查询。
- Web Intent 检查 domain approval、browser 计数和
NON_BROWSER_ONLY。 - 选择器显示异常检查目标 user、badging、label/icon 和 dock metadata。
- 与
queryIntentActivities结果不一致时,检查 resolveForStart 的非 exported 后置过滤。
9. 源码阅读路线
PackageManager的resolveActivity契约。ResolveIntentHelper.resolveIntentInternal:入口安全检查和候选查询。chooseBestActivity:明确赢家、preferred、Instant App 和 ResolverActivity。ComputerEngine.applyPostResolutionFilter:理解 query 阶段动态 split/Web 结果如何影响单选。PackageManagerService中 system installer/uninstaller 查找调用:观察系统如何消费唯一结果。
resolveActivity 是候选列表之上的策略层。它的正确性取决于候选排序、用户偏好、域名验证、调用场景和私有 flags;因此遇到“为什么没有直接返回第一项”时,应沿这些分支逐一核对。
