Skip to content

Intent 优先级与默认应用

分析 IntentFilter priority、安全限幅、PreferredActivity 和 ResolverActivity 的真实选择路径。

AndroidPMSIntentPreferredActivity

Intent 优先级与默认应用 ​

多个 Activity 都匹配同一个 Intent 时,Android 并不是简单地“优先级最高者永远胜出”。Android 17 先在组件注册阶段限制危险的 filter priority,再在查询阶段排序,最后由 ResolveIntentHelper.chooseBestActivity 判断是否使用用户首选项、Instant App 域名批准或 ResolverActivity。

1. 三个阶段 ​

阶段owner关键状态
注册限幅ComponentResolverIntentFilter.priority
候选排序IntentResolver / resolverResolveInfo 字段
单结果选择ResolveIntentHelperPreferredActivity / ResolverActivity

2. 结果排序 ​

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

java
public static final Comparator<ResolveInfo> RESOLVE_PRIORITY_SORTER = (r1, r2) -> {
    int v1 = r1.priority;
    int v2 = r2.priority;
    if (v1 != v2) return (v1 > v2) ? -1 : 1;

    v1 = r1.preferredOrder;
    v2 = r2.preferredOrder;
    if (v1 != v2) return (v1 > v2) ? -1 : 1;

    if (r1.isDefault != r2.isDefault) {
        return r1.isDefault ? -1 : 1;
    }
    v1 = r1.match;
    v2 = r2.match;
    if (v1 != v2) return (v1 > v2) ? -1 : 1;

    if (r1.system != r2.system) return r1.system ? -1 : 1;
    if (r1.activityInfo != null) {
        return r1.activityInfo.packageName.compareTo(
                r2.activityInfo.packageName);
    }
    if (r1.serviceInfo != null) {
        return r1.serviceInfo.packageName.compareTo(
                r2.serviceInfo.packageName);
    }
    if (r1.providerInfo != null) {
        return r1.providerInfo.packageName.compareTo(
                r2.providerInfo.packageName);
    }
    return 0;
};

排序顺序是 priority、preferredOrder、isDefault、match、system、包名。priority 来自 filter;match 是 IntentFilter.match() 的 data 质量分数;system 只在前几项都相同才参与。因此“系统应用一定排第一”并不正确。

3. priority 限幅 ​

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

java
private static final Set<String> PROTECTED_ACTIONS = new ArraySet<>();
static {
    PROTECTED_ACTIONS.add(Intent.ACTION_SEND);
    PROTECTED_ACTIONS.add(Intent.ACTION_SENDTO);
    PROTECTED_ACTIONS.add(Intent.ACTION_SEND_MULTIPLE);
    PROTECTED_ACTIONS.add(Intent.ACTION_VIEW);
}

private void adjustPriority(@NonNull Computer computer,
        List<ParsedActivity> systemActivities,
        ParsedActivity activity, ParsedIntentInfo intentInfo,
        String setupWizardPackage) {
    IntentFilter intentFilter = intentInfo.getIntentFilter();
    if (intentFilter.getPriority() <= 0) return;

    String packageName = activity.getPackageName();
    var packageState = computer.getPackageStateInternal(packageName);
    final boolean privilegedApp = packageState.isPrivileged();
    if (!privilegedApp) {
        intentFilter.setPriority(0);
        return;
    }

非 privileged app 的正 priority 会被强制改为 0。这个修改发生在 filter 注册阶段,后续 ResolveInfo.priority 看到的是被限幅后的值,而不是 manifest 中原始值。

4. 受保护 action ​

java
if (isProtectedAction(intentFilter)) {
    if (mDeferProtectedFilters) {
        if (mProtectedFilters == null) {
            mProtectedFilters = new ArrayList<>();
        }
        mProtectedFilters.add(Pair.create(activity, intentInfo));
    } else {
        if (packageName.equals(setupWizardPackage)) {
            return;
        }
        intentFilter.setPriority(0);
    }
    return;
}

SEND、SENDTO、SEND_MULTIPLE、VIEW 属于受保护 action。系统扫描尚未完成时,resolver 不能知道哪个包是 Setup Wizard,因此先保存 filter;扫描结束后由 fixProtectedFilterPriorities 统一处理。Setup Wizard 是唯一允许在这些 action 上保留高 priority 的例外。

5. 更新包上限 ​

java
if (systemActivities == null) {
    // 系统分区中的 privileged app 可使用声明的 priority
    return;
}

ParsedActivity foundActivity =
        findMatchingActivity(systemActivities, activity);
if (foundActivity == null) {
    intentFilter.setPriority(0);
    return;
}

final List<ParsedIntentInfo> intentListCopy =
        new ArrayList<>(foundActivity.getIntents());

未打包更新的 privileged app 不能凭新增 Activity 获得高 priority;只有系统版本中存在同名 Activity,才继续比较 filter 是否等价。

java
getIntentListSubset(intentListCopy,
        ParsedIntentInfo::actionsIterator,
        intentFilter.actionsIterator());
getIntentListSubset(intentListCopy,
        ParsedIntentInfo::categoriesIterator,
        intentFilter.categoriesIterator());
getIntentListSubset(intentListCopy,
        ParsedIntentInfo::schemesIterator,
        intentFilter.schemesIterator());
getIntentListSubset(intentListCopy,
        ParsedIntentInfo::authoritiesIterator,
        intentFilter.authoritiesIterator());

if (intentListCopy.size() == 0) {
    intentFilter.setPriority(0);
    return;
}

int cappedPriority = 0;
for (int i = intentListCopy.size() - 1; i >= 0; --i) {
    cappedPriority = Math.max(cappedPriority,
            intentListCopy.get(i).getIntentFilter().getPriority());
}
if (intentFilter.getPriority() > cappedPriority) {
    intentFilter.setPriority(cappedPriority);
}

更新包会依次比较 action、category、scheme、authority 子集;任一关键集合不等价就降为 0。等价 filter 也只能把 priority 限制在系统版本对应 filter 的最高值,不能超过出厂版本。

6. 单选消费 ​

源码文件: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);
    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);
        }
        ResolveInfo preferred =
                mPreferredActivityHelper.findPreferredActivityNotLocked(
                        computer, intent, resolvedType, flags,
                        query, true, false, debug,
                        userId, queryMayBeFiltered);
        if (preferred != null) return preferred;

query 已由 resolver 排序。只有前两名的 priority、preferredOrder、isDefault 全相同,才查用户偏好;因此 PreferredActivity 不是无条件覆盖高 priority 的机制。

7. 偏好持久化 ​

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

java
public ResolveInfo findPreferredActivityNotLocked(
        @NonNull Computer snapshot, Intent intent,
        String resolvedType, long flags, List<ResolveInfo> query,
        boolean always, boolean removeMatches, boolean debug,
        int userId, boolean queryMayBeFiltered) {
    if (Thread.holdsLock(mPm.mLock)) {
        Slog.wtf(TAG, "Calling thread is holding mLock", new Throwable());
    }
    if (!mPm.mUserManager.exists(userId)) return null;
    PackageManagerService.FindPreferredActivityBodyResult body =
            snapshot.findPreferredActivityInternal(
                    intent, resolvedType, flags, query,
                    always, removeMatches, debug,
                    userId, queryMayBeFiltered);
    if (body.mChanged) {
        mPm.scheduleWritePackageRestrictions(userId);
    }
    return body.mPreferredResolveInfo;
}

PreferredActivity 查询要求不持有 PMS 主锁,使用 Computer 快照查找;如果查询过程中清理了过期偏好,才调度 package-restrictions 持久化。读取偏好本身不是纯字符串比较,还要与当前 query 候选集合和 userId 对齐。

8. 添加默认偏好 ​

java
public void addPreferredActivity(@NonNull Computer snapshot,
        WatchedIntentFilter filter, int match,
        ComponentName[] set, ComponentName activity,
        boolean always, int userId,
        String opname, boolean removeExisting) {
    final int callingUid = Binder.getCallingUid();
    snapshot.enforceCrossUserPermission(
            callingUid, userId, true, false,
            "add preferred activity");
    if (mPm.mContext.checkCallingOrSelfPermission(
            android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
            != PackageManager.PERMISSION_GRANTED) {
        mPm.mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
    }
    if (filter.countActions() == 0) {
        Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
        return;
    }

写入偏好需要跨用户权限和 SET_PREFERRED_APPLICATIONS。过滤器没有 action 时直接拒绝,因为没有稳定的 Intent 匹配键。

java
synchronized (mPm.mLock) {
    final PreferredIntentResolver pir =
            mPm.mSettings.editPreferredActivitiesLPw(userId);
    final ArrayList<PreferredActivity> existing =
            pir.findFilters(filter);
    if (removeExisting && existing != null) {
        Settings.removeFilters(pir, filter, existing);
    }
    pir.addFilter(mPm.snapshotComputer(),
            new PreferredActivity(filter, match, set, activity, always));
    mPm.scheduleWritePackageRestrictions(userId);
}

写入在锁内修改该用户的 PreferredIntentResolver,随后写入 package restrictions。always 决定“始终使用”还是临时 last-chosen 语义;set 保存当时参与选择的完整候选集合,用于之后验证偏好是否仍适用。

9. ResolverActivity ​

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);

无明确 priority 差异、无 PreferredActivity、无已批准 Instant App 时,普通 resolve 返回 ResolverActivity;RESOLVE_NON_RESOLVER_ONLY 则让同一场景返回 null。ResolverActivity 是系统构造的选择器结果,不是候选列表中的真实业务 Activity。

10. 排查顺序 ​

  1. 先打印每个 ResolveInfo 的 priority、preferredOrder、isDefault、match 和 system。
  2. 若第三方 priority 大于 0,检查 ComponentResolver.adjustPriority 是否已将其限为 0。
  3. 若是 privileged 更新包,比较系统版本和更新包的 Activity/filter 是否等价。
  4. 若前三个排序字段相同,检查 PreferredActivity 是否存在、候选 set 是否仍匹配。
  5. 若返回 ResolverActivity,检查 RESOLVE_NON_RESOLVER_ONLY、Instant App 域名批准和 browserCount。
  6. 修改默认应用后检查 userId、package restrictions 写入和 preferred activity changed 广播。

11. 源码阅读路线 ​

  1. ComponentResolver.RESOLVE_PRIORITY_SORTER:确认排序字段顺序。
  2. ComponentResolver.adjustPriority / fixProtectedFilterPriorities:确认 priority 安全限幅。
  3. ResolveIntentHelper.chooseBestActivity:确认静态排序、PreferredActivity 和 ResolverActivity 的消费顺序。
  4. PreferredActivityHelper.findPreferredActivityNotLocked:确认快照读取与持久化触发。
  5. PreferredActivityHelper.addPreferredActivity:确认写权限、候选 set 和 userId。
  6. PackageManagerService 中的默认 installer、uninstaller、home 查找:观察系统服务如何依赖单结果选择。

默认应用选择的核心不是“谁 priority 高谁赢”,而是注册时先限制 priority,查询时再排序,最后在排序相同的候选集合中应用用户偏好或选择器。把这三层混在一起,会导致对 Android Intent 结果的错误判断。