ComputerEngine
ComputerEngine 是 Android 17 Package Manager 的只读查询实现。它并不负责安装、卸载或把状态写回 Settings,而是把 PMS 当前可查询的数据组织成一个 Computer,再由这个对象执行包、组件、UID、共享库和 Intent 查询。
本篇的学习目标是读懂“一个查询结果怎样产生”,而不是记住 ComputerEngine 有多少方法。文章会沿源码追踪四件事:
Computer为什么规定查询函数必须是纯函数,ComputerEngine为什么同时保存快照字段和少量服务引用;queryIntentActivitiesInternal()怎样处理用户、flags、显式组件、selector、instant app、跨 profile 和后过滤;getInstalledApplications()怎样在已安装、未安装、归档、APEX、共享库和可见性之间做分支;ComputerLocked为什么只覆盖极少数方法,以及这些方法如何避免读取未稳定发布的数据。
PackageManagerInternal 已经解释过“system_server 如何拿到 Computer”,ComputerEngine 则回答“拿到之后具体查什么、按什么顺序查”。下一篇会专门讨论 snapshot 失效、重建、版本和锁的并发机制;本篇只在需要理解查询语义的地方引用这些入口。
1. 查询契约
1.1 Computer 的约束
源码文件:frameworks/base/services/core/java/com/android/server/pm/Computer.java
/**
* A {@link Computer} provides a set of functions that can operate on live data or snapshot
* data. At this time, the {@link Computer} is implemented by the
* {@link ComputerEngine}, which is in turn extended by {@link ComputerLocked}.
*
* New functions must be added carefully.
* <ol>
* <li> New functions must be true functions with respect to data collected in a
* {@link PackageManagerService.Snapshot}. Such data may never be modified from inside a {@link Computer}
* function.
* </li>
*
* <li> A new function must be implemented in {@link ComputerEngine}.
* </li>
*
* <li> A new function must be overridden in {@link ComputerLocked} if the function
* cannot safely access live data without holding the PackageManagerService lock. The
* form of the {@link ComputerLocked} function must be a single call to the
* {@link ComputerEngine} implementation, wrapped in a <code>synchronized</code>
* block. Functions in {@link ComputerLocked} should never include any other code.
* </li>
* </ol>
*/
public interface Computer extends PackageDataSnapshot {这段注释就是查询引擎的设计合同:
Computer函数针对Snapshot中的数据必须是“true function”,不能在查询过程中修改这些数据。- 新函数先在
ComputerEngine实现;只有 live data 不能安全读取时,才在ComputerLocked覆盖。 ComputerLocked的覆盖只能是“加锁后调用ComputerEngine实现”,不能把业务逻辑复制一份。
因此看到一个查询方法时,先问它读取的是 snapshot 字段、未快照化的服务引用,还是 PMS live state。这个判断比方法名更重要。
1.2 查询接口的分组
Computer 接口把查询按结果和策略分成几组:
源码文件:frameworks/base/services/core/java/com/android/server/pm/Computer.java
int getVersion();
Computer use();
default int getUsed() { return 0; }
@NonNull List<ResolveInfo> queryIntentActivitiesInternal(
Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
int filterCallingUid, int callingPid, int userId,
boolean resolveForStart, boolean allowDynamicSplits);
ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
ActivityInfo getActivityInfoInternal(ComponentName component, long flags,
int filterCallingUid, int userId);
AndroidPackage getPackage(String packageName);
AndroidPackage getPackage(int uid);
ApplicationInfo getApplicationInfo(String packageName, long flags, int userId);
ApplicationInfo getApplicationInfoInternal(String packageName, long flags,
int filterCallingUid, int userId);
PackageInfo getPackageInfo(String packageName, long flags, int userId);
PackageInfo getPackageInfoInternal(String packageName, long versionCode, long flags,
int filterCallingUid, int userId);方法大致分为三层:
- 数据读取:
getPackage()、getPackageStateInternal()、getPackagesForUid();返回AndroidPackage或PackageStateInternal等内部对象。 - 对象生成:
getApplicationInfoInternal()、getPackageInfoInternal()、getActivityInfoInternal();把包模型和 per-user 状态转换成 framework 返回对象。 - 解析与策略:
queryIntentActivitiesInternal()、shouldFilterApplication()、updateFlagsForResolve();先确定候选集合,再应用用户、权限、instant app 和可见性规则。
这三层不是互相独立的。Intent 查询会调用组件解析器,再调用 getActivityInfo() 或后过滤;应用列表会从 PackageStateInternal 生成 ApplicationInfo,并在生成前后调用过滤策略。
2. 引擎数据
2.1 Snapshot参数
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
private final int mVersion;
// The administrative use counter.
private int mUsed = 0;
// Cached attributes. The names in this class are the same as the
// names in PackageManagerService; see that class for documentation.
protected final Settings mSettings;
private final WatchedSparseIntArray mIsolatedOwners;
private final WatchedArrayMap<String, AndroidPackage> mPackages;
private final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation;
private final SharedLibrariesRead mSharedLibraries;
private final ComponentName mLocalResolveComponentName;
private final ActivityInfo mResolveActivity;
private final WatchedSparseBooleanArray mWebInstantAppsDisabled;
private final ActivityInfo mLocalInstantAppInstallerActivity;
private final ResolveInfo mInstantAppInstallerInfo;
private final InstantAppRegistry mInstantAppRegistry;
private final ApplicationInfo mLocalAndroidApplication;
private final AppsFilterSnapshot mAppsFilter;
private final WatchedArrayMap<String, Integer> mFrozenPackages;这些字段可以按“查询需要什么”理解:
| 字段 | 查询责任 | 数据是否属于快照 |
|---|---|---|
mSettings | 包设置、共享 UID、per-user 状态 | 是,使用 new Settings(args.settings) 包装 |
mPackages | 已解析的 AndroidPackage | 是 |
mComponentResolver | Activity、Service、Provider、Receiver 匹配 | 是接口引用,内部快照由 resolver 提供 |
mAppsFilter | package visibility | 是 |
mSharedLibraries | 共享库查找与依赖 | 是 |
mIsolatedOwners | isolated UID 到 owner UID 的映射 | 是 |
mFrozenPackages | 安装/更新期间的冻结包 | 是 |
mPermissionManager | 权限检查与 app-op 包查询 | 引用外部服务,不复制 |
mUserManager | user 存在、跨 user/profile 权限 | 引用外部服务 |
mContext、mInjector | 资源、兼容性和依赖入口 | 引用外部服务 |
表中最后一组“引用外部服务”很关键:ComputerEngine 不是把 PMS 全部字段深拷贝一遍。它只复制需要稳定读取的可观察数据;对于从未由 PMS lock 保护、或必须使用实时服务的对象,保留引用并依靠这些服务自己的并发合同。
2.2 复制边界
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
ComputerEngine(PackageManagerService.Snapshot args, int version) {
mVersion = version;
mSettings = new Settings(args.settings);
mIsolatedOwners = args.isolatedOwners;
mPackages = args.packages;
mSharedLibraries = args.sharedLibraries;
mInstrumentation = args.instrumentation;
mWebInstantAppsDisabled = args.webInstantAppsDisabled;
mLocalResolveComponentName = args.resolveComponentName;
mResolveActivity = args.resolveActivity;
mLocalInstantAppInstallerActivity = args.instantAppInstallerActivity;
mInstantAppInstallerInfo = args.instantAppInstallerInfo;
mInstantAppRegistry = args.instantAppRegistry;
mLocalAndroidApplication = args.androidApplication;
mAppsFilter = args.appsFilter;
mFrozenPackages = args.frozenPackages;
mComponentResolver = args.componentResolver;
// The following are not cached copies. Instead they are
// references to outside services.
mPermissionManager = args.service.mPermissionManager;
mUserManager = args.service.mUserManager;
mContext = args.service.mContext;
mInjector = args.service.mInjector;
mApexManager = args.service.mApexManager;
mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
mDefaultAppProvider = args.service.getDefaultAppProvider();
mDomainVerificationManager = args.service.mDomainVerificationManager;
mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
mService = args.service;
}args.settings、args.packages、args.appsFilter 等来自 PMS 的 Snapshot,它们构成查询视图;权限服务、UserManager 和 Context 则直接来自 PMS service。最后的 mService 只用于少量 primitive 或未受 PMS lock 管理的属性。
阅读某个新字段时,应该先判断它属于哪一类:如果字段是 args 中的快照成员,查询应保持只读;如果字段来自 args.service,要继续查该服务自己的线程安全和锁顺序。
2.3 版本和使用计数
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public int getVersion() {
return mVersion;
}
/** Record that the snapshot was used. */
public final Computer use() {
mUsed++;
return this;
}
/** Return the usage counter. */
public final int getUsed() {
return mUsed;
}use() 返回自身,所以 PMS 可以在返回 snapshot 的同时累加使用次数。mUsed 主要服务于 snapshot 统计,并不参与查询结果,也不是引用计数释放机制。不要据此推断 snapshot 会在 getUsed() 归零后销毁;生命周期由 PMS 的 snapshot 缓存管理。
3. 包与对象
3.1 原始包对象
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public AndroidPackage getPackage(String packageName) {
packageName = resolveInternalPackageName(
packageName, PackageManager.VERSION_CODE_HIGHEST);
return mPackages.get(packageName);
}
@Override
public AndroidPackage getPackage(int uid) {
final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
AndroidPackage pkg = null;
final int numPackages = packageNames == null ? 0 : packageNames.length;
for (int i = 0; pkg == null && i < numPackages; i++) {
pkg = mPackages.get(packageNames[i]);
}
return pkg;
}getPackage(String) 先通过 resolveInternalPackageName() 处理重命名包和静态库,再做 map lookup;它本身不按 user 过滤。getPackage(int uid) 复用 getPackagesForUidInternal(uid, Process.SYSTEM_UID),再从结果中找到第一个有 AndroidPackage 的对象。shared UID 对应多个包,所以这个返回值不能被当成“UID 对应唯一包”;需要完整成员集合时,应使用 shared-user 专门的查询方法。
3.2 状态与包
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public final PackageStateInternal getPackageStateInternal(String packageName) {
return getPackageStateInternal(packageName, Binder.getCallingUid());
}
public PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
packageName = resolveInternalPackageNameInternalLocked(
packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
return mSettings.getPackage(packageName);
}
@Override
public PackageStateInternal getPackageStateFiltered(@NonNull String packageName,
int callingUid, @UserIdInt int userId) {
packageName = resolveInternalPackageNameInternalLocked(
packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
var packageState = mSettings.getPackage(packageName);
if (shouldFilterApplication(packageState, callingUid, userId)) {
return null;
}
return packageState;
}getPackageStateInternal() 的两个重载都会先规范化重命名包,然后直接返回 Settings 状态;传入 callingUid 并不自动执行 visibility 过滤。真正按 user 和 calling UID 过滤的是单独的 getPackageStateFiltered()。这是一个重要边界:需要隐藏 package existence 的调用方必须选择 filtered 变体,不能仅仅调用带 UID 参数的 internal 变体。
PackageStateInternal 可能存在而 getPkg() 为 null。这表示 PMS 仍保留包状态,但当前没有可用的解析包对象,例如未安装保留数据或解析内容尚未恢复。后面的 generateApplicationInfoFromSettings() 正是为这种状态保留的分支。
3.3 ApplicationInfo
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public final ApplicationInfo getApplicationInfo(String packageName, long flags, int userId) {
final int callingUid = Binder.getCallingUid();
return getApplicationInfoInternal(packageName, flags, callingUid, userId);
}
@Override
public final ApplicationInfo getApplicationInfoInternal(String packageName, long flags,
int filterCallingUid, int userId) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForApplication(flags, userId);
if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"get application info");
}
return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId);
}调用顺序是:检查 user→调整 application flags→执行 cross-user 权限检查→进入 getApplicationInfoInternalBody()。body 会规范化重命名包,处理 APEX、共享库和 visibility,再根据 AndroidPackage 是否存在选择 PackageInfoUtils 或 Settings 生成路径。把“入口权限检查”和“对象生成”分成两层,是 ComputerEngine 中常见的结构。
4. Intent 查询
4.1 入口guard
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(
Intent intent, String resolvedType, long flags,
int filterCallingUid, int userId) {
return queryIntentActivitiesInternal(
intent, resolvedType, flags, 0 /*privateResolveFlags*/, filterCallingUid,
Process.INVALID_PID, userId,
/*resolveForStart*/ false, /*allowDynamicSplits*/ true);
}这个重载没有自行解析 Intent,而是补齐默认参数进入完整实现。默认值代表普通“查询 activities”场景:不带 private resolve flags、没有有效 calling PID、不为启动动作专门解析、允许 dynamic splits。
完整实现的第一步不是匹配组件,而是验证 user 和调整 flags:
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(
Intent intent, String resolvedType, long flags,
long privateResolveFlags, int filterCallingUid, int callingPid, int userId,
boolean resolveForStart, boolean allowDynamicSplits) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
// Allow to match activities of quarantined packages.
flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");这里有三个容易漏掉的状态:
- user 不存在直接返回空列表,不会进入 resolver;
- 查询内部会补上
MATCH_QUARANTINED_COMPONENTS,所以后续匹配允许处理被 quarantine 的组件; filterCallingUid用于结果过滤,而跨 user 权限检查使用真实Binder.getCallingUid()。本地调用方若清掉 Binder identity,仍必须把原始过滤 UID 作为参数保留下来。
4.2 selector与组件
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
final String pkgName = intent.getPackage();
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
comp != null || pkgName != null /*onlyExposedExplicitly*/,
isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId, resolvedType,
flags));
var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
false /* isReceiver */, resolveForStart, filterCallingUid, callingPid);
args.platformCompat = mInjector.getCompatibility();
args.snapshot = this;Intent 可能有 selector。源码先保存原始 Intent,再把 selector 作为真正的匹配输入;匹配完成后还会恢复 args.intent = originalIntent,重新验证所有组件是否满足原始 Intent。这样可以避免 selector 只满足内部查询而原始约束被绕过。
updateFlagsForResolve() 还根据调用者、是否启动、显式组件和 camera intent 调整 flags。它不是一个简单的 bitwise OR 工具,而是把 instant app 能见范围和特殊 intent 策略集中到解析前。
4.3 显式组件分支
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
List<ResolveInfo> list = Collections.emptyList();
boolean skipPostResolution = false;
if (comp != null) {
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null) {
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean matchExplicitlyVisibleOnly =
(flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
final boolean isCallerInstantApp = instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(ai.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetVisibleToInstantApp =
(ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
final boolean blockInstantResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& !isTargetVisibleToInstantApp));
final boolean resolveForStartNonExported = resolveForStart
&& !ai.exported
&& !isCallerSameApp(pkgName, filterCallingUid);
final boolean blockNormalResolution =
(!resolveForStart || resolveForStartNonExported)
&& !isTargetInstantApp
&& !isCallerInstantApp
&& shouldFilterApplication(
getPackageStateInternal(ai.applicationInfo.packageName,
Process.SYSTEM_UID), filterCallingUid, userId);
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
ri.userHandle = UserHandle.of(userId);
list = new ArrayList<>(1);
list.add(ri);
SaferIntentUtils.enforceIntentFilterMatching(args, list);
}
}
}显式组件并不意味着“只要类存在就返回”。源码仍检查:
- caller 和 target 是否是 instant app,以及目标是否声明对 instant app 可见;
- 启动场景下非 exported activity 是否属于 caller 自己;
- 普通调用者是否能看到目标 package;
- 最终
ResolveInfo是否满足原始 Intent filter。
只有通过这些条件才构造单元素列表。若 getActivityInfo() 返回 null 或任一 block 条件为真,结果保持空列表。
4.4 隐式候选
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
QueryIntentActivitiesResult lockedResult =
queryIntentActivitiesInternalBody(
intent, resolvedType, flags, filterCallingUid, userId,
resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
if (lockedResult.answer != null) {
skipPostResolution = true;
list = lockedResult.answer;
} else {
if (lockedResult.addInstant) {
String callingPkgName = getInstantAppPackageName(filterCallingUid);
boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
lockedResult.result = maybeAddInstantAppInstaller(
lockedResult.result, intent, resolvedType, flags,
userId, resolveForStart, isRequesterInstantApp);
}
if (lockedResult.sortResult) {
lockedResult.result.sort(RESOLVE_PRIORITY_SORTER);
}
list = lockedResult.result;
}
SaferIntentUtils.blockNullAction(args, list);queryIntentActivitiesInternalBody() 返回一个带控制信息的结果,而不是简单的 List。answer 非空时表示 body 已经给出最终答案,外层跳过 post-resolution filter;否则外层根据 addInstant 添加 instant app installer,根据 sortResult 排序,再阻止没有 action 的非法结果。
这种返回结构把“候选列表”和“后续阶段是否仍需运行”一起传递,避免外层重复推断。读源码时不要只追 result 字段,也要追 answer、addInstant 和 sortResult。
4.5 profile与package
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
Intent intent, String resolvedType, long flags, int filterCallingUid, int userId,
boolean resolveForStart, boolean allowDynamicSplits,
String pkgName, String instantAppPkgName) {
boolean sortResult = false;
boolean addInstant = false;
List<ResolveInfo> result = new ArrayList<>();
List<CrossProfileDomainInfo> crossProfileResults = new ArrayList<>();
if (pkgName == null) {
if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
resolvedType, userId)) {
final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
intent, resolvedType, flags, userId);
if (queryResult != null) {
result.addAll(filterIfNotSystemUser(queryResult, userId));
}
}
addInstant = isInstantAppResolutionAllowed(intent, result,
userId, false /*skipPackageCheck*/, flags);
boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
resolvedType, userId, flags, pkgName, hasNonNegativePriorityResult,
resolveForStart, mSettings::getPackage);
if (intent.hasWebURI() || !crossProfileResults.isEmpty()) sortResult = true;
} else {
final PackageStateInternal setting =
getPackageStateInternal(pkgName, Process.SYSTEM_UID);
if (setting != null && setting.getAndroidPackage() != null
&& (resolveForStart || !shouldFilterApplication(setting,
filterCallingUid, userId))) {
final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
userId);
if (queryResult != null) {
result.addAll(filterIfNotSystemUser(queryResult, userId));
}
}
if (result.size() == 0) {
addInstant = isInstantAppResolutionAllowed(intent, null /*result*/, userId,
true /*skipPackageCheck*/, flags);
}
crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
resolvedType, userId, flags, pkgName, false,
resolveForStart, mSettings::getPackage);
}隐式查询有两条主要路径:
- 没有 package 限定:先检查是否跳过当前 profile,再让
ComponentResolver查询 activities;之后判断是否允许 instant app,并解析跨 profile 结果。 - 指定 package:先从 Settings 找 package state,用
Process.SYSTEM_UID做包状态 lookup,再用真实filterCallingUid决定是否过滤;如果当前包没有结果,才尝试 instant app fallback。
无论哪条路径,组件匹配和跨 profile 解析都发生在同一个 ComputerEngine 上,后续还要合并结果、处理 preferred activity 和排序。
4.6 后过滤
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
if (originalIntent != null) {
// We also have to ensure all components match the original intent
args.intent = originalIntent;
SaferIntentUtils.enforceIntentFilterMatching(args, list);
}
return skipPostResolution ? list : applyPostResolutionFilter(
list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
resolveForStart, userId, intent);这里是 Intent 查询的最后一道边界:
- selector 场景重新以原始 Intent 验证结果;
- 普通结果进入
applyPostResolutionFilter(),继续处理 instant app 可见性、动态 split 和启动场景; - body 已经给出
answer时跳过后过滤,因为 body 已经承担了最终结果的构造。
4.7 Service差异
queryIntentServicesInternal() 复用相似结构,但 service 查询的参数和 post filter 不完全相同:
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final @NonNull List<ResolveInfo> queryIntentServicesInternal(
Intent intent, String resolvedType, long flags, int userId,
int callingUid, int callingPid, boolean includeInstantApps,
boolean resolveForStart) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
enforceCrossUserOrProfilePermission(Binder.getCallingUid(), userId,
false /*requireFullPermission*/, false /*checkShell*/,
"query intent services");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);service 查询使用 enforceCrossUserOrProfilePermission(),而 activity 查询使用 enforceCrossUserPermission();这体现了 profile 路由上的不同权限要求。service 的 callingUid、callingPid 和 resolveForStart 也会传进 SaferIntentUtils.IntentArgs,不能因为结果类型都是 ResolveInfo 就把两条路径当成同一套策略。
5. 应用列表
5.1 入口条件
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@NonNull
@Override
public List<ApplicationInfo> getInstalledApplications(
@PackageManager.ApplicationInfoFlagsBits long flags,
@UserIdInt int userId, int callingUid, boolean forceAllowCrossUser) {
if (getInstantAppPackageName(callingUid) != null) {
return Collections.emptyList();
}
if (!mUserManager.exists(userId)) return Collections.emptyList();
flags = updateFlagsForApplication(flags, userId);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
final boolean listArchivedOnly = !listUninstalled
&& (flags & MATCH_ARCHIVED_PACKAGES) != 0;
if (!forceAllowCrossUser) {
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"get installed application info");
}列表查询先处理三个“直接为空”的条件:instant app caller、不存在的 user、跨 user 权限不足。然后从 flags 得出三个分支变量:是否包含已知但未安装包、是否包含 APEX、是否仅列归档包。
forceAllowCrossUser 是内部调用方的特权开关,但它只跳过这一个 cross-user permission check,不会跳过后面的 package visibility 过滤。不能把它理解成“返回全部包”。
5.2 未安装与归档分支
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
final ArrayMap<String, ? extends PackageStateInternal> packageStates =
getPackageStates();
if (listUninstalled || listArchivedOnly) {
list = new ArrayList<>(packageStates.size());
for (PackageStateInternal ps : packageStates.values()) {
ApplicationInfo ai;
long effectiveFlags = flags;
if (ps.isSystem()) {
effectiveFlags |= PackageManager.MATCH_ANY_USER;
}
if (ps.getPkg() != null) {
if (!listApex && ps.getPkg().isApex()) {
continue;
}
PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
if (listArchivedOnly && !userState.isInstalled()
&& userState.getArchiveState() == null) {
continue;
}
if (filterSharedLibPackage(ps, callingUid, userId)) {
continue;
}
if (shouldFilterApplication(ps, callingUid, userId)) {
continue;
}
ai = PackageInfoUtils.generateApplicationInfo(ps.getPkg(), effectiveFlags,
ps.getUserStateOrDefault(userId), userId, ps);
if (ai != null) {
ai.packageName = resolveExternalPackageName(ps.getPkg());
}
} else {
// Shared lib filtering done in generateApplicationInfoFromSettingsLPw
// and already converts to externally visible package name
ai = generateApplicationInfoFromSettings(ps.getPackageName(),
effectiveFlags, callingUid, userId);
}
if (ai != null) {
list.add(ai);
}
}
}这个分支遍历的是全部 PackageStateInternal,不是只有 mPackages。因此它能表达“状态还在但 APK 不可用”的包。每个状态依次经过:
- system package 补
MATCH_ANY_USER; - APEX 包开关;
- 归档状态判断;
- shared library 过滤;
shouldFilterApplication()可见性判断;- 根据
pkg是否为空选择对象生成器。
MATCH_KNOWN_PACKAGES 和 MATCH_ARCHIVED_PACKAGES 只扩大候选状态集合,不会取消 shouldFilterApplication()。这就是 flags 与 visibility 的关系:flags 决定“考虑哪些状态”,calling UID 决定“结果能否交给调用方”。
5.3 普通已安装分支
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
} else {
list = new ArrayList<>(mPackages.size());
for (PackageStateInternal packageState : packageStates.values()) {
final AndroidPackage pkg = packageState.getPkg();
if (pkg == null) {
continue;
}
if (!listApex && pkg.isApex()) {
continue;
}
if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
continue;
}
if (filterSharedLibPackage(packageState, callingUid, userId)) {
continue;
}
if (shouldFilterApplication(packageState, callingUid, userId)) {
continue;
}
ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags,
packageState.getUserStateOrDefault(userId), userId, packageState);
if (ai != null) {
ai.packageName = resolveExternalPackageName(pkg);
list.add(ai);
}
}
}
return list;普通分支更严格:pkg == null、APEX 不在 flags、用户未安装、共享库过滤或 visibility 失败都会跳过。这里没有调用 generateApplicationInfoFromSettings(),因为普通列表只接受有解析包对象的已安装状态。
注意遍历来源仍是 packageStates.values(),而不是 mPackages.values()。这样可以在一个循环中统一处理 Settings 状态、per-user 安装位和解析包对象,避免两个 map 在更新期间产生不同的候选集合。
6. 可见性策略
6.1 过滤重载
源码文件:frameworks/base/services/core/java/com/android/server/pm/Computer.java
boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid,
@Nullable ComponentName component, @PackageManager.ComponentType int componentType,
int userId, boolean filterUninstall);
boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid,
int userId);
boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
int callingUid, int userId);
boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
int callingUid, int userId);
boolean shouldFilterApplicationIncludingUninstalledNotArchived(
@Nullable PackageStateInternal ps, int callingUid, int userId);方法重载体现了不同信息泄露风险:
- 普通
shouldFilterApplication()主要判断当前 user 是否可见、是否安装以及 component 是否可访问; IncludingUninstalled把“不存在”和“当前用户未安装”统一纳入过滤,避免通过返回差异泄露包存在性;IncludingUninstalledNotArchived额外把 archived 包排除;- shared UID 使用
SharedUserSetting重载,因为一个 appId 下所有成员的可见性需要按 group 语义判断。
6.2 AppsFilter策略
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
@PackageManager.ComponentType int componentType, int userId,
boolean filterUninstall, boolean filterArchived) {
if (Process.isSdkSandboxUid(callingUid)) {
int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
// SDK sandbox should be able to see its client app.
if (ps != null && clientAppUid == UserHandle.getUid(userId, ps.getAppId())) {
return false;
}
}
// If we're in an isolated process, get the real calling UID.
if (Process.isIsolated(callingUid)) {
callingUid = getIsolatedOwner(callingUid);
}
final String instantAppPkgName = getInstantAppPackageName(callingUid);
final boolean callerIsInstantApp = instantAppPkgName != null;
final boolean packageArchivedForUser = ps != null
&& PackageArchiver.isArchived(ps.getUserStateOrDefault(userId));
if (ps == null
|| (filterUninstall && !isSystemOrRootOrShell(callingUid)
&& !ps.isHiddenUntilInstalled()
&& !ps.getUserStateOrDefault(userId).isInstalled()
&& (!packageArchivedForUser || filterArchived))) {
return callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid);
}
if (isCallerSameApp(ps.getPackageName(), callingUid)) {
return false;
}
if (callerIsInstantApp) {
if (ps.getUserStateOrDefault(userId).isInstantApp()) {
return true;
}
if (component != null) {
return !isComponentVisibleToInstantApp(component, componentType);
}
return !ps.getPkg().isVisibleToInstantApps();
}
if (ps.getUserStateOrDefault(userId).isInstantApp()) {
if (canViewInstantApps(callingUid, userId)) {
return false;
}
if (component != null) {
return true;
}
return !mInstantAppRegistry.isInstantAccessGranted(
userId, UserHandle.getAppId(callingUid), ps.getAppId());
}
final int appId = UserHandle.getAppId(callingUid);
final SettingBase callingPs = mSettings.getSettingBase(appId);
return mAppsFilter.shouldFilterApplication(this, callingUid, callingPs, ps, userId);
}源码中的完整实现还会根据 component type、instant app、system/root/shell 和 shared user 处理更多分支;这里最重要的结构是:先处理空状态和 user install,再把 package visibility 交给 AppsFilterSnapshot,最后才按组件类型判断 component-level visibility。
ComputerEngine 不会把 AppsFilter 的图算法复制一份。它传入自身作为 snapshot,使 AppsFilter 在同一个包状态视图上计算 caller 与 target 的关系。
6.3 UID反查
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public String[] getPackagesForUid(int uid) {
return getPackagesForUidInternal(uid, Binder.getCallingUid());
}
private String[] getPackagesForUidInternal(int uid, int callingUid) {
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
if (Process.isSdkSandboxUid(uid)) {
uid = getBaseSdkSandboxUid();
}
if (isKnownIsolatedComputeApp(uid)) {
uid = getIsolatedOwner(uid);
}
final int appId = UserHandle.getAppId(uid);
return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
}
protected String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
boolean isCallerInstantApp) {
final Object obj = mSettings.getSettingBase(appId);
if (obj instanceof SharedUserSetting) {
if (isCallerInstantApp) {
return null;
}
final SharedUserSetting sus = (SharedUserSetting) obj;
final ArraySet<PackageStateInternal> packageStates =
(ArraySet<PackageStateInternal>) sus.getPackageStates();
String[] res = new String[packageStates.size()];
int i = 0;
for (PackageStateInternal ps : packageStates) {
if (ps.getUserStateOrDefault(userId).isInstalled()
&& !shouldFilterApplication(ps, callingUid, userId)) {
res[i++] = ps.getPackageName();
}
}
return ArrayUtils.trimToSize(res, i);
} else if (obj instanceof PackageStateInternal) {
final PackageStateInternal ps = (PackageStateInternal) obj;
if (ps.getUserStateOrDefault(userId).isInstalled()
&& !shouldFilterApplication(ps, callingUid, userId)) {
return new String[]{ps.getPackageName()};
}
}
return null;
}UID 反查先处理 SDK sandbox 和 isolated compute UID,再按 appId 找 SharedUserSetting 或 PackageStateInternal。shared UID 只返回当前 user 已安装且未被过滤的成员;instant app caller 直接得到 null。普通 UID 返回单元素数组,不可见或未安装时返回 null。源码还特别说明该 Binder API 有缓存,算法或底层 setting 变化时必须同步检查缓存失效点。
7. ComputerLocked
7.1 这个子类做什么
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerLocked.java
/**
* This subclass is the external interface to the live computer. Some internal helper
* methods are overridden to fetch live data instead of snapshot data.
*/
public final class ComputerLocked extends ComputerEngine {
ComputerLocked(PackageManagerService.Snapshot args) {
super(args, -1);
}
protected ComponentName resolveComponentName() {
return mService.getResolveComponentName();
}
protected ActivityInfo instantAppInstallerActivity() {
return mService.mInstantAppInstallerActivity;
}
protected ApplicationInfo androidApplication() {
return mService.getCoreAndroidApplication();
}
}Android 17 的 ComputerLocked 并没有重写大量查询方法,它只替换三个 protected accessor:resolve component、instant app installer activity 和 core Android application。构造时 version 为 -1,表示这是 live computer,而不是普通编号 snapshot。
7.2 Live边界
Computer 注释要求:只有读取 live data 不安全时才在 ComputerLocked 覆盖,并且覆盖函数只能在锁下调用基类实现。原因有两面:
- 不覆盖时,查询直接走稳定快照,读线程不用持有 PMS lock;
- 过度覆盖时,查询可能遍历正在修改的 resolver、Settings 或 per-user 状态,读到不一致集合。
ComputerLocked 的存在不是“性能更快的 Computer”,而是为少数必须观察当前 live 属性的内部路径提供一个受锁保护的入口。任何新方法都应先证明 snapshot 数据不足,再考虑覆盖。
8. Snapshot入口
8.1 创建引擎
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@GuardedBy("mLock")
private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
var now = SystemClock.currentTimeMicro();
var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
var args = new Snapshot(Snapshot.SNAPPED);
var newSnapshot = new ComputerEngine(args, newVersion);
var done = SystemClock.currentTimeMicro();
if (mSnapshotStatistics != null) {
mSnapshotStatistics.rebuild(now, done, hits,
newSnapshot.getPackageStates().size());
}
return newSnapshot;
}
/** Create a live computer. */
private ComputerLocked createLiveComputer() {
return new ComputerLocked(new Snapshot(Snapshot.LIVE));
}rebuildSnapshot() 在 PMS lock 下创建 Snapshot.SNAPPED 参数,再交给 ComputerEngine 保存字段。createLiveComputer() 使用 Snapshot.LIVE 和 ComputerLocked。查询引擎本身不知道 snapshot 何时失效、何时发布;这些属于 PMS 的缓存管理逻辑。
8.2 快照与查询的时序
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@NonNull
public Computer snapshotComputer() {
return snapshotComputer(true /*allowLiveComputer*/);
}
if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
return oldSnapshot.use();
}调用方拿到的是一个已经选择好的 Computer。之后 ComputerEngine 只在这个对象上读数据;如果中途 PMS 状态变化,当前查询不会自动切换到新版本。下一篇会详细展开版本失效和重建竞争,本篇只需记住:一个查询链应尽量复用同一个 Computer,避免同一业务在两个版本间拼接结果。
9. 共享库与权限查询
9.1 共享库使用者查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
@NonNull SharedLibraryInfo libInfo, long flags,
int callingUid, @UserIdInt int userId) {
List<VersionedPackage> versionedPackages = null;
final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
final int packageCount = packageStates.size();
List<Boolean> usesLibsOptional = null;
for (int i = 0; i < packageCount; i++) {
PackageStateInternal ps = packageStates.valueAt(i);
if (ps == null) {
continue;
}
if (!PackageUserStateUtils.isAvailable(ps.getUserStateOrDefault(userId), flags)) {
continue;
}
final String libName = libInfo.getName();
if (libInfo.isStatic() || libInfo.isSdk()) {
final String[] libs = libInfo.isStatic()
? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
final long[] libsVersions = libInfo.isStatic()
? ps.getUsesStaticLibrariesVersions()
: ps.getUsesSdkLibrariesVersionsMajor();
final boolean[] libsOptional = libInfo.isSdk()
? ps.getUsesSdkLibrariesOptional() : null;
final int libIdx = ArrayUtils.indexOf(libs, libName);
if (libIdx < 0 || libsVersions[libIdx] != libInfo.getLongVersion()) {
continue;
}
if (shouldFilterApplication(ps, callingUid, userId)) {
continue;
}
if (versionedPackages == null) {
versionedPackages = new ArrayList<>();
usesLibsOptional = new ArrayList<>();
}
String dependentPackageName = ps.getPackageName();
if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) {
dependentPackageName = ps.getPkg().getManifestPackageName();
}
versionedPackages.add(new VersionedPackage(dependentPackageName, ps.getVersionCode()));
usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]);
} else if (ps.getPkg() != null) {
if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName)
|| ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) {
if (shouldFilterApplication(ps, callingUid, userId)) {
continue;
}
if (versionedPackages == null) {
versionedPackages = new ArrayList<>();
}
versionedPackages.add(new VersionedPackage(ps.getPackageName(),
ps.getVersionCode()));
}
}
}
return Pair.create(versionedPackages, usesLibsOptional);
}共享库查询展示了 ComputerEngine 的典型“状态扫描”模式:先按 user availability 过滤,再按库类型、名称和版本匹配,最后按 calling UID 做 visibility 过滤。即使调用者知道某包声明了库,也不能绕过 package visibility。
9.2 权限查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
@Override
public int checkUidPermission(String permName, int uid) {
return mPermissionManager.checkUidPermission(permName, uid);
}
@Override
public String[] getAppOpPermissionPackages(@NonNull String permissionName, int userId) {
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "getAppOpPermissionPackages");
if (permissionName == null || getInstantAppPackageName(callingUid) != null
|| !mUserManager.exists(userId)) {
return EmptyArray.STRING;
}
final ArraySet<String> packageNames = new ArraySet(
mPermissionManager.getAppOpPermissionPackages(permissionName));
for (int i = packageNames.size() - 1; i >= 0; i--) {
final String packageName = packageNames.valueAt(i);
if (!shouldFilterApplicationIncludingUninstalled(
mSettings.getPackage(packageName), callingUid, userId)) {
continue;
}
packageNames.removeAt(i);
}
return packageNames.toArray(new String[packageNames.size()]);
}权限本身由 PermissionManagerServiceInternal owner 计算,ComputerEngine 只负责调用边界、user 检查和 visibility 二次过滤。这个分工避免权限表和包可见性表互相复制,同时确保返回的 package name 不暴露不可见包。
10. 消费者与调用约束
10.1 PMS Binder 路径
IPackageManagerBase 中的简单 Binder 方法通常是:
源码文件:frameworks/base/services/core/java/com/android/server/pm/IPackageManagerBase.java
@Override
@Deprecated
public final PackageInfo getPackageInfo(String packageName,
long flags, int filterCallingUid, int userId) {
return snapshot().getPackageInfoInternal(packageName,
PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId);
}从 Binder 进入后,Base 取 Computer,再把 package、flags、filter UID 和 user 传给引擎。这里没有把 IPackageManager 的 calling UID 直接交给 ComputerEngine 的 Binder.getCallingUid() 替代,因为 Base 已经明确保留了 filterCallingUid 语义。
10.2 内部路径
PermissionPolicyService 等本地服务通过 PackageManagerInternal 调用 snapshot() 或便利代理。典型代码是:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
final Computer snapshot = mPackageManagerInternal.snapshot();
final AndroidPackage pkg = snapshot.getPackage(packageName);
final PackageStateInternal state = snapshot.getPackageStateInternal(packageName);相较于连续调用 mPackageManagerInternal.getPackage()、getPackageStateInternal(),显式 snapshot 能保证两次读取来自同一查询视图。它仍不会让外部服务获得 PMS 写锁,也不会把内部对象变成线程安全可变对象;调用方必须遵循只读和生命周期约束。
10.3 典型错误用法
以下写法会让查询链失去一致性:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
// 不推荐:每次方法调用都可能取得不同版本的 Computer。
AndroidPackage pkg = pmi.getPackage(packageName);
PackageInfo info = pmi.getPackageInfo(packageName, flags, callingUid, userId);如果 package 在两次调用之间更新,pkg 和 info 可能来自不同 snapshot;如果调用方再把它们写入自己的缓存,就会形成跨版本组合。更稳妥的写法是:
Computer snapshot = pmi.snapshot();
AndroidPackage pkg = snapshot.getPackage(packageName);
PackageInfo info = snapshot.getPackageInfoInternal(
packageName, PackageManager.VERSION_CODE_HIGHEST, flags, callingUid, userId);11. 失败与边界
11.1 返回空列表的条件
ComputerEngine 大量查询使用空列表表达“没有结果”,但原因可能不同:
| 条件 | 典型方法 | 语义 |
|---|---|---|
| user 不存在 | queryIntentActivitiesInternal、getInstalledApplications | 参数指向无效 user,未进入匹配 |
| instant app caller | 应用列表、共享库列表 | instant app 被禁止枚举全局包 |
| package 不可见 | getApplicationInfoInternal、UID 反查 | 隐藏目标,避免存在性侧信道 |
| 没有已安装 state | installed applications 普通分支 | 状态存在但当前 user 未安装 |
| 没有解析包 | getPackage()、对象生成 | 只有 Settings 状态,无法生成 manifest 对象 |
调试时应结合 flags、user state 和 calling UID 判断原因,不能只记录“返回空”。
11.2 异常与哨兵值
不同 API 对失败的契约不同:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@Override
public long getCeDataInode(String packageName, int userId) {
final PackageStateInternal packageState =
getPackageStateInternal(packageName);
if (packageState == null) {
return 0;
}
return packageState.getUserStateOrDefault(userId).getCeDataInode();
}
@Override
public boolean wasPackageEverLaunched(String packageName, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(packageName);
if (packageState == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
return !packageState.getUserStateOrDefault(userId).isNotLaunched();
}inode 查询用 0 表达错误或不存在,启动历史查询则抛 IllegalArgumentException。同一个引擎中的方法也不能统一套用“空值表示失败”或“异常表示失败”的规则。
11.3 查询不负责写回
ComputerEngine 的接口约束禁止在查询函数内修改 snapshot 数据。查询可能触发少量统计,例如 use() 计数,但不会把 enabled、suspended、preferred 或 permission state 写回 PMS。若解析过程中发现 dangling preferred activity,清理动作由更高层 helper 在允许 mutation 的路径完成,而不是由普通 Computer 查询悄悄修改 Settings。
12. 测试证据
12.1 AppsFilterTest
测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
assertFalse(appsFilter.shouldFilterApplication(
mSnapshot, DUMMY_CALLING_APPID, calling, target, USER_ID));
assertTrue(appsFilter.shouldFilterApplication(
mSnapshot, DUMMY_CALLING_APPID, calling, target, USER_ID));
watcher.verifyNoChangeReported("shouldFilterApplication");这类测试准备 calling/target package state 与一个 snapshot,然后改变签名、queryable 关系或组件条件,断言 shouldFilterApplication() 的 true/false,并确认查询没有修改 watcher 观察到的状态。它证明的是 AppsFilter 的纯判断和可见性规则,不证明 ComputerEngine 的 Intent 候选排序、PMS snapshot 重建或跨 user 权限。
12.2 ParserTest
测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
pkg.hideAsFinal();
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
pkg, 0, PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(pkg));
assertNotNull(appInfo);输入是一个解析完成的 package 和默认 user state,动作是调用 PackageInfoUtils.generateApplicationInfo(),断言生成结果非空。它覆盖“包模型 + user state 能生成 ApplicationInfo”,但没有覆盖 ComputerEngine 在生成前做的 shouldFilterApplication(),也没有覆盖 resolveExternalPackageName() 的外部名称转换。
12.3 该补什么测试
如果要为新 ComputerEngine 查询增加测试,至少应拆成三层:
- 候选层:给定 package/component 集合,确认 resolver 返回哪些原始候选。
- 策略层:改变 user state、calling UID、instant app 标志,确认可见性和 cross-profile 过滤。
- 对象层:确认
PackageInfoUtils生成的ApplicationInfo/ResolveInfo字段和外部 package name。
还应明确未证明的部分:snapshot 重建竞争、真实 Binder identity、APEX 扫描状态、磁盘持久化和安装卸载事件需要更高层测试,不能用单独的 PackageInfoUtils 或 AppsFilter 单测替代。
13. 阅读路线
面对 ComputerEngine.java 中近六千行代码,可以固定使用下面的源码阅读顺序:
- 先在
Computer.java找方法签名和 Javadoc,记录 user、calling UID、flags 以及返回值的失败语义。 - 在
ComputerEngine找同名实现,先读 guard 条件,再读候选收集,最后读对象生成和 post filter。 - 遇到
mSettings,进入Settings/PackageStateInternal;遇到mComponentResolver,进入 resolver snapshot;遇到mAppsFilter,进入 AppsFilter snapshot。 - 对 Intent 方法,按
updateFlagsForResolve → ComponentResolver → cross-profile → instant app → post filter顺序画调用图。 - 对列表方法,区分
PackageStateInternal集合与mPackages集合,再记录每个 flags 分支是否包含未安装、归档和 APEX。 - 检查
ComputerLocked是否覆盖该方法;若没有,默认按稳定 snapshot 读取,若有,继续确认覆盖是否只做 live accessor 或锁包装。 - 最后读测试,把输入、动作、断言和未覆盖范围分别写出来。
小结
Android 17 的 ComputerEngine 不是一个简单的“包查询工具类”,而是一套有明确边界的只读计算引擎:
Computer要求查询函数对 snapshot 数据保持纯读,新函数先落在ComputerEngine,只有 live data 不安全时才由ComputerLocked覆盖。- 构造函数只复制需要稳定读取的 Settings、包、resolver、AppsFilter 等数据;权限、UserManager、Context 等服务保持外部引用。
- Intent 查询不是一次 resolver 调用,而是 user/flags guard、selector 处理、显式或隐式候选收集、跨 profile、instant app、原始 Intent 复核和 post filter 的连续链路。
- 应用列表根据
MATCH_KNOWN_PACKAGES、MATCH_ARCHIVED_PACKAGES和MATCH_APEX分支遍历 package state,再经过 availability、shared library、visibility 和对象生成。 shouldFilterApplication()既处理包可见性,也处理未安装、归档、shared UID 和 component-level visibility;flags 扩大候选范围,但不会自动取消这些策略。- 查询返回空列表、
null、哨兵值或异常各有具体契约,必须回到方法实现判断原因。
继续阅读时,最自然的下一步是进入 Computer 的 snapshot 产生与失效机制:同一个 ComputerEngine 如何被缓存、何时被 onChange() 标记过期、ComputerLocked 又怎样与 PMS lock 协作。这些内容属于并发与快照专题,不应混在本篇查询算法中。
