ResolveIntentHelper
ResolveIntentHelper 的职责不是执行 IntentFilter.match(),而是编排一次解析:修正 flags、检查目标用户、调用 Computer 查询候选、应用启动场景安全规则,并从候选中选出唯一结果或系统 ResolverActivity。它还为 Receiver、Service、Provider 提供不同的入口语义。
1. 依赖决定职责
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
final class ResolveIntentHelper {
private final Context mContext;
private final PlatformCompat mPlatformCompat;
private final UserManagerService mUserManager;
private final PreferredActivityHelper mPreferredActivityHelper;
private final DomainVerificationManagerInternal mDomainVerificationManager;
private final UserNeedsBadgingCache mUserNeedsBadging;
private final Supplier<ResolveInfo> mResolveInfoSupplier;
private final Supplier<ActivityInfo> mInstantAppInstallerActivitySupplier;
}这些字段对应五类横切逻辑:用户存在性和跨用户访问、兼容性控制的安全过滤、用户首选项、Web 域名批准、ResolverActivity/Instant App Installer 的系统模板。组件过滤器索引不在这里,而在 Computer 持有的 ComponentResolverApi 快照中。
2. Activity 入口
源码文件: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);真实调用者 callingUid 用于跨用户权限检查,filterCallingUid 用于包可见性和结果过滤,两者并非始终相同。系统内部可以代表另一个 UID 查询,但不能因此绕过 Binder 调用者的跨用户权限。
resolvedType 已由外层解析,helper 不重新向 ContentResolver 查询 MIME type。allowDynamicSplits=true 允许 Activity 查询在需要时加入 instant/dynamic split 结果。
3. 启动安全过滤
if (resolveForStart) {
var args = new SaferIntentUtils.IntentArgs(
intent, resolvedType, false /* isReceiver */, true,
filterCallingUid, callingPid);
args.platformCompat = mPlatformCompat;
SaferIntentUtils.filterNonExportedComponents(args, query);
}
final boolean queryMayBeFiltered =
UserHandle.getAppId(filterCallingUid)
>= Process.FIRST_APPLICATION_UID
&& !resolveForStart;启动场景显式过滤非 exported 组件;普通查询场景则记录结果“可能被包可见性过滤”,交给首选 Activity 逻辑处理。resolveForStart 不是性能开关,而是安全语义:启动和仅查询可见候选的结果范围可能不同。
4. 最终浏览器限制
final ResolveInfo bestChoice = chooseBestActivity(
computer, intent, resolvedType, flags,
privateResolveFlags, query, userId, queryMayBeFiltered);
final boolean nonBrowserOnly =
(privateResolveFlags
& PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
if (nonBrowserOnly && bestChoice != null
&& bestChoice.handleAllWebDataURI) {
return null;
}
return bestChoice;RESOLVE_NON_BROWSER_ONLY 在最佳结果产生后检查。如果最终是通用浏览器,helper 返回 null;它不是在候选查询前删除所有浏览器。这个顺序对 App Links 和 fallback 行为非常关键。
5. 最佳结果
5.1 零个、一个、多个
private ResolveInfo chooseBestActivity(
Computer computer, Intent intent, String resolvedType,
long flags, long privateResolveFlags,
List<ResolveInfo> query, int userId,
boolean queryMayBeFiltered) {
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,一个结果直接返回。多个结果只比较排序后前两项:priority、preferredOrder、isDefault 任一不同,第一项足以胜出。match 已参与前面的列表排序,但这里不把 match 差异作为直接单选条件。
5.2 用户首选项
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
ResolveInfo ri = mPreferredActivityHelper.findPreferredActivityNotLocked(
computer, intent, resolvedType, flags, query,
true /* always */, false /* removeMatches */,
debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}只有排序关键字段相同才查 preferred activity。queryMayBeFiltered 告知 helper 当前候选可能不是完整集合,防止普通应用基于不可见包推断用户默认选择。
5.3 Instant 域名
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
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 用于判断是否所有候选都是通用浏览器,并把该属性传给系统 resolver 的结果。
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 禁止返回系统 resolver,歧义无法消解时直接 null。否则复制 supplier 提供的系统模板,设置 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;若 Intent 限定 package 且所有结果来自该包,resolver 使用目标应用的 label/icon;工作资料用户需要 badging 时不直接复用资源。非 user 0 还要重写 resolver 的 UID。METADATA_DOCK_HOME 保证系统 resolver 能在 car/dock 场景显示。
7. Receiver 入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
public List<ResolveInfo> queryIntentReceiversInternal(
Computer computer, Intent intent, String resolvedType,
long flags, int userId, int filterCallingUid,
int callingPid, boolean forSend) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int queryingUid = forSend
? Process.SYSTEM_UID : filterCallingUid;
computer.enforceCrossUserPermission(
queryingUid, userId, false, false,
"query intent receivers");发送广播时 forSend=true,过滤身份使用 system UID;普通查询使用 filterCallingUid。这不会跳过后续可见性与 SaferIntentUtils,而是改变用于过滤 receiver 的查询身份。
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
var args = new SaferIntentUtils.IntentArgs(
intent, resolvedType, true /* isReceiver */,
forSend, filterCallingUid, callingPid);
args.platformCompat = mPlatformCompat;
args.snapshot = computer;Receiver 的 IntentArgs.isReceiver=true,安全策略与 Activity 不同。显式 Receiver 仍检查 Instant App 可见性并执行 filter matching;隐式 Receiver 还会调用 blockNullAction。
8. Service 入口差异
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
public ResolveInfo resolveServiceInternal(
Computer computer, Intent intent, String resolvedType,
long flags, int userId, int callingUid,
int callingPid, boolean resolveForStart) {
if (!mUserManager.exists(userId)) return null;
flags = computer.updateFlagsForResolve(
flags, userId, callingUid,
false /* includeInstantApps */, false);
List<ResolveInfo> query = computer.queryIntentServicesInternal(
intent, resolvedType, flags, userId, callingUid,
callingPid, false, resolveForStart);
if (query != null && query.size() >= 1) {
return query.get(0);
}
return null;
}Service 不构造 ResolverActivity;多个同优先级结果直接取第一项。服务启动不能弹出用户选择 UI,因此其歧义处理与 Activity 天生不同。
9. 启动发送器
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
List<ResolveInfo> ris = computer.queryIntentActivitiesInternal(
intentToResolve, resolvedType, 0, 0,
callingUid, callingPid, userId, true, false);
if (ris == null || ris.isEmpty()) {
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
ris = computer.queryIntentActivitiesInternal(
intentToResolve, resolvedType, 0, 0,
callingUid, callingPid, userId, true, false);
}启动 IntentSender 先找 CATEGORY_INFO,失败后回退 CATEGORY_LAUNCHER。即使两次都无结果,方法仍创建一个不带 component 的 IntentSender;真正发送时才抛 SendIntentException。这是“创建成功但使用失败”的恢复/失败边界。
10. 排查顺序
- 用户不存在:Activity/Service 返回 null,Receiver/Provider 查询返回空列表。
- query 有结果但 resolve 为 null:检查
RESOLVE_NON_BROWSER_ONLY、RESOLVE_NON_RESOLVER_ONLY和非 exported 过滤。 - 多结果未弹 ResolverActivity:比较前两项 priority、preferredOrder、isDefault,以及 preferred activity。
- Web Intent 直接命中 Instant App:检查 domain approval。
- Receiver 查询与实际发送结果不同:确认
forSend改变了 querying UID。 - Service 多结果行为异常:Service 只取排序后的第一项,不走用户选择器。
- Launch IntentSender 无法发送:检查 CATEGORY_INFO 与 CATEGORY_LAUNCHER 两次查询是否都为空。
11. 源码阅读路线
resolveIntentInternal:用户、flags、跨用户检查和候选查询。chooseBestActivity:唯一项、排序字段、preferred、Instant App 和 ResolverActivity。queryIntentReceiversInternal:发送广播与只查询时的身份差异。resolveServiceInternal:无 ResolverActivity 的单选语义。getLaunchIntentSenderForPackage:INFO 到 LAUNCHER 的回退及延迟失败。ComputerEngine.queryIntentActivitiesInternal:继续追踪 helper 下沉后的显式/隐式查询。
ResolveIntentHelper 的价值在于把“候选查询”和“可执行的最终选择”分开。只有理解调用身份、启动语义和私有 flags,才能解释相同 Intent 为什么在 query、resolve、start 和 send 场景中得到不同结果。
