Skip to content

resolveActivity

分析 resolveActivity 如何从候选列表选择目标、应用首选项并处理 ResolverActivity。

AndroidPMSPackageManagerIntent

resolveActivity ​

queryIntentActivities 返回候选列表,resolveActivity 则要把列表收敛成一个 ResolveInfo。Android 17 的单选逻辑并不只是取第一项:它先依赖查询阶段排序,再比较 priority/preferredOrder/isDefault,随后查用户首选项、Instant App 域名批准,最后才构造 ResolverActivity 或返回 null。

1. 查询与单选的边界 ​

阶段方法结果
候选生成Computer.queryIntentActivitiesInternalList<ResolveInfo>
单选编排ResolveIntentHelper.resolveIntentInternal一个 ResolveInfo 或 null
歧义消解chooseBestActivity目标 Activity、ResolverActivity 或 null

2. 解析入口 ​

源码文件: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);

用户检查、flags 修正和跨用户检查发生在查询之前。callingUid 是真实 Binder 身份,filterCallingUid 参与包可见性;二者分离是系统服务代查场景的重要安全边界。

java
        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

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. 用户首选 ​

java
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 与浏览器 ​

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 同时统计通用 Web handler,供后面设置 ResolverActivity 的 handleAllWebDataURI。

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 是内部调用的“不要选择器”约束。未设置时,supplier 提供系统 ResolverActivity 模板,helper 复制 ActivityInfo 后填充 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;

指定 package 且所有候选来自同一包时,选择器借用该包的 label/icon;需要 badging 时不直接返回资源 ID。非主用户会重写 UID,METADATA_DOCK_HOME 保证车载/dock 场景可展示。

7. 内部调用示例 ​

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

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

  1. 先看 query 是否为空,再看 resolve 是否因 NON_RESOLVER_ONLY 返回 null。
  2. 多候选时比较 priority、preferredOrder、isDefault,确认是否进入 preferred 查询。
  3. Web Intent 检查 domain approval、browser 计数和 NON_BROWSER_ONLY。
  4. 选择器显示异常检查目标 user、badging、label/icon 和 dock metadata。
  5. 与 queryIntentActivities 结果不一致时,检查 resolveForStart 的非 exported 后置过滤。

9. 源码阅读路线 ​

  1. PackageManager 的 resolveActivity 契约。
  2. ResolveIntentHelper.resolveIntentInternal:入口安全检查和候选查询。
  3. chooseBestActivity:明确赢家、preferred、Instant App 和 ResolverActivity。
  4. ComputerEngine.applyPostResolutionFilter:理解 query 阶段动态 split/Web 结果如何影响单选。
  5. PackageManagerService 中 system installer/uninstaller 查找调用:观察系统如何消费唯一结果。

resolveActivity 是候选列表之上的策略层。它的正确性取决于候选排序、用户偏好、域名验证、调用场景和私有 flags;因此遇到“为什么没有直接返回第一项”时,应沿这些分支逐一核对。