Skip to content

ResolveIntentHelper

分析 ResolveIntentHelper 的入口校验、安全过滤、最佳 Activity 选择与组件差异。

AndroidPMSPackageManagerIntent

ResolveIntentHelper ​

ResolveIntentHelper 的职责不是执行 IntentFilter.match(),而是编排一次解析:修正 flags、检查目标用户、调用 Computer 查询候选、应用启动场景安全规则,并从候选中选出唯一结果或系统 ResolverActivity。它还为 Receiver、Service、Provider 提供不同的入口语义。

1. 依赖决定职责 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ResolveIntentHelper.java

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

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. 启动安全过滤 ​

java
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. 最终浏览器限制 ​

java
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 零个、一个、多个 ​

java
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

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

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

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 对应标签和目标用户。

java
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

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

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

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

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. 排查顺序 ​

  1. 用户不存在:Activity/Service 返回 null,Receiver/Provider 查询返回空列表。
  2. query 有结果但 resolve 为 null:检查 RESOLVE_NON_BROWSER_ONLY、RESOLVE_NON_RESOLVER_ONLY 和非 exported 过滤。
  3. 多结果未弹 ResolverActivity:比较前两项 priority、preferredOrder、isDefault,以及 preferred activity。
  4. Web Intent 直接命中 Instant App:检查 domain approval。
  5. Receiver 查询与实际发送结果不同:确认 forSend 改变了 querying UID。
  6. Service 多结果行为异常:Service 只取排序后的第一项,不走用户选择器。
  7. Launch IntentSender 无法发送:检查 CATEGORY_INFO 与 CATEGORY_LAUNCHER 两次查询是否都为空。

11. 源码阅读路线 ​

  1. resolveIntentInternal:用户、flags、跨用户检查和候选查询。
  2. chooseBestActivity:唯一项、排序字段、preferred、Instant App 和 ResolverActivity。
  3. queryIntentReceiversInternal:发送广播与只查询时的身份差异。
  4. resolveServiceInternal:无 ResolverActivity 的单选语义。
  5. getLaunchIntentSenderForPackage:INFO 到 LAUNCHER 的回退及延迟失败。
  6. ComputerEngine.queryIntentActivitiesInternal:继续追踪 helper 下沉后的显式/隐式查询。

ResolveIntentHelper 的价值在于把“候选查询”和“可执行的最终选择”分开。只有理解调用身份、启动语义和私有 flags,才能解释相同 Intent 为什么在 query、resolve、start 和 send 场景中得到不同结果。