PMS Binder接口
本文面向已经读过 Binder调用源码地图 和 PackageSetting 的读者。前者建立 Java Binder 的 Proxy/Stub/驱动基础,后者说明 PMS 查询最终消费哪些包状态;本文把两条线接起来,从应用调用 PackageManager.getPackageInfo() 开始,追到 framework 缓存、IPackageManager Proxy、system_server Stub、IPackageManagerBase、IPackageManagerImpl 和 Computer snapshot,再沿返回值回到调用线程。
Android 17 的 PMS 不直接继承 IPackageManager.Stub。真正注册到 ServiceManager 的对象是内部类 IPackageManagerImpl,它继承 IPackageManagerBase;Base 再继承 AIDL 生成的 Stub。Base 收纳大量“获取一次 snapshot 后直接委托”的 final 方法,Impl 承担需要权限、身份切换、锁、异步任务或其他 helper 的复杂方法。这一层次是阅读 227 个 AIDL 方法的入口,不应在 8800 多行 PMS 主类里逐个盲搜。
本文不会把 227 个方法逐行翻译成表格。方法会按状态 owner、同步模型和返回协议分类,并选择五条具有代表性的完整路径:单包查询、大列表查询、隐藏应用、清除数据、oneway dex/usage 通知。读完后应能判断一个新 Binder API 应放在 Base 还是 Impl,权限应在哪一层执行,何时需要保留原 calling UID,以及为何 callback 方法返回并不代表操作已经完成。
1. 接口边界
1.1 AIDL规模
源码文件:frameworks/base/core/java/android/content/pm/IPackageManager.aidl
/**
* See {@link PackageManager} for documentation on most of the APIs here.
*
* @hide
*/
interface IPackageManager {
void checkPackageStartable(String packageName, int userId);
boolean isPackageAvailable(String packageName, int userId);
PackageInfo getPackageInfo(String packageName, long flags, int userId);
PackageInfo getPackageInfoVersioned(in VersionedPackage versionedPackage,
long flags, int userId);
int getPackageUid(String packageName, long flags, int userId);
int[] getPackageGids(String packageName, long flags, int userId);
}按当前 AIDL 逐个 method terminator 统计,共有 227 个 transaction method,其中只有 3 个 oneway。接口覆盖查询、Intent 解析、权限/签名、安装卸载、用户状态、preferred/domain、存储/Dex、instant/archive、observer 和系统配置。它是 platform-internal AIDL,没有 stable AIDL 的 VINTF version/hash 契约;framework client 与 system_server 来自同一 platform release。
常见数据方向也体现接口用途:in 传 VersionedPackage、Intent、filter、callback;out/inout 用于旧式 List 输出,例如 querySyncProviders;ParcelFileDescriptor 传 metadata/profile 文件;callback Binder 传异步完成;ParceledListSlice/PackageInfoList 传可能过大的列表。
1.2 三层实现
源码文件:frameworks/base/services/core/java/com/android/server/pm/IPackageManagerBase.java
/**
* Contains all simply proxy methods which need a snapshot instance and just calls a method on it,
* with no additional logic. Separated with all methods marked final and deprecated to prevent their
* use from other methods which may need a snapshot for non-trivial reasons.
*/
public abstract class IPackageManagerBase extends IPackageManager.Stub {
@NonNull
private final PackageManagerService mService;
protected Computer snapshot() {
return mService.snapshotComputer();
}源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public class IPackageManagerImpl extends IPackageManagerBase {
public IPackageManagerImpl() {
super(PackageManagerService.this, mContext, mDexOptHelper, mModuleInfoProvider,
mPreferredActivityHelper, mResolveIntentHelper, mDomainVerificationManager,
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
mServicesExtensionPackageName, mSharedSystemSharedLibraryPackageName);
}AIDL compiler 生成的 IPackageManager.Stub 负责 transaction code、Parcel 解码、调用 override 与 reply 编码;生成文件不作为手写源码保存在该目录,因此本文不伪造 onTransact() switch。可直接确认的手写继承链是:
IPackageManager.Stub (generated)
-> IPackageManagerBase (simple snapshot/helper proxies)
-> PackageManagerService.IPackageManagerImpl (complex implementations)Base 的方法标记 final @Deprecated 不是说 AIDL 已废弃,而是阻止 PMS 内部代码把 Binder facade 当普通 helper 调用。复杂内部调用应显式获取同一 Computer snapshot并传下去,避免一次业务操作中不知不觉拿多个版本。
1.3 服务发布
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
IPackageManagerImpl iPackageManager = m.new IPackageManagerImpl();
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);Java IPackageManager 对应 package,native 精简接口对应 package_native。两者不是同一个 Stub,也不共享 AIDL。本文只讲 Java package;PackageManagerNative 有独立专题。
2. 客户端入口
2.1 PM对象
源码文件:frameworks/base/core/java/android/app/ContextImpl.java
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
// Doesn't matter if we make more than one instance, so no synchronization
// is needed on mPackageManager.
mPackageManager = getPackageManagerInner();
return mPackageManager;
}
private PackageManager getPackageManagerInner() {
final IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
return new ApplicationPackageManager(this, pm);
}
return null;
}应用拿到的是 context-scoped ApplicationPackageManager,它保存 ContextImpl 和进程级 IPackageManager。Context 提供默认 userId、opPackageName、资源与权限语境;同一个 Binder proxy 可被多个 Context 的 facade 复用。
2.2 服务发现
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
return sPackageManager;
}
final IBinder b = ServiceManager.getService("package");
sPackageManager = IPackageManager.Stub.asInterface(b);
return sPackageManager;
}首次调用从 servicemanager 查询 package handle,再用 generated Stub.asInterface() 判断本地/远程对象。应用进程得到 Proxy,system_server 内部若直接传本地 Binder 可能得到本地接口。sPackageManager 是进程级 volatile cache,后续 framework API 不重复服务发现。
2.3 Framework异常
源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java
public PackageInfo getPackageInfoAsUser(
String packageName, PackageInfoFlags flags, int userId)
throws NameNotFoundException {
PackageInfo pi = getPackageInfoAsUserCached(
packageName,
updateFlagsForPackage(flags.getValue(), userId),
userId);
if (pi == null) {
throw new NameNotFoundException(packageName);
}
return pi;
}Binder 返回 null 不会原样暴露给 public API,而被转换为 NameNotFoundException。null 可能表示包确实不存在、目标用户不存在、包不可用或包可见性过滤;public API 有意把这些情况收敛为“调用方看不到该包”。RemoteException 则通过 rethrowFromSystemServer() 变成合适的 runtime exception。
3. 单包查询
3.1 Client cache
源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java
private static PackageInfo getPackageInfoAsUserUncached(
String packageName, long flags, int userId) {
try {
return ActivityThread.getPackageManager()
.getPackageInfo(packageName, flags, userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache = new PropertyInvalidatedCache<>(
new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
.maxEntries(MAX_INFO_CACHE_ENTRIES)
.api(packageInfoApi()).cacheNulls(true),
"getPackageInfo", null) {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
return getPackageInfoAsUserUncached(
query.packageName, query.flags, query.userId);
}
};cache key 是 packageName、flags、userId,null 结果也缓存。PMS 状态写路径必须 invalidate 对应 property,否则应用会继续得到旧 PackageInfo或旧“不可见”。PMS构造函数 已说明 system_server 初始化期间为什么 cork/disable这些 cache。
3.2 Base委托
源码文件:frameworks/base/services/core/java/com/android/server/pm/IPackageManagerBase.java
@Override
@Deprecated
public final PackageInfo getPackageInfo(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
return snapshot().getPackageInfo(packageName, flags, userId);
}该方法只做一次 snapshot acquisition 和委托,因此放在 Base。没有额外 synchronized:Computer snapshot 提供 point-in-time read view,内部再执行用户存在、跨用户权限、静态库/包可见性与 PackageInfo 生成。
3.3 Computer过滤
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
public final PackageInfo getPackageInfo(String packageName,
long flags, int userId) {
return getPackageInfoInternal(
packageName, PackageManager.VERSION_CODE_HIGHEST,
flags, Binder.getCallingUid(), userId);
}
public final PackageInfo getPackageInfoInternal(
String packageName, long versionCode, long flags,
int filterCallingUid, int userId) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForPackage(flags, userId);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"get package info");
return getPackageInfoInternalBody(
packageName, versionCode, flags, filterCallingUid, userId);
}Binder.getCallingUid() 用于权限检查;filterCallingUid 用于包可见性。可信内部调用可在 clearCallingIdentity 后仍传原始 UID做过滤,所以方法将两者分开。普通 Binder 调用两者相同。
AndroidPackage p = mPackages.get(packageName);
var packageState = mSettings.getPackage(packageName);
if (p != null) {
final PackageStateInternal ps =
getPackageStateInternal(p.getPackageName());
if (!matchApex && p.isApex()) {
return null;
}
if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
return null;
}
if (ps != null
&& shouldFilterApplication(ps, filterCallingUid, userId)) {
return null;
}
return generatePackageInfo(ps, flags, userId);
}包可见性发生在生成 PackageInfo 前。调用方无权看到目标时返回 null,不泄露版本、签名、组件数量或“包其实存在”。MATCH_KNOWN_PACKAGES/MATCH_ARCHIVED_PACKAGES 还允许从没有 active AndroidPackage 的 PackageSetting 生成最小 PackageInfo,具体字段已在 PackageSetting 专题说明。
4. 接口分工
4.1 方法族
227 个方法可以按最终 owner 分类,而不是按 AIDL 文件顺序阅读:
| 方法族 | 典型方法 | 主要实现/owner | 返回模型 |
|---|---|---|---|
| 单包/组件查询 | getPackageInfo、getApplicationInfo、getActivityInfo | Computer | object/null |
| Intent/provider解析 | resolveIntent、queryIntentActivities、resolveContentProvider | Computer/ResolveIntentHelper | ResolveInfo/list |
| UID/签名/keyset | getPackagesForUid、checkSignatures、getSigningKeySet | Computer/KeySetManager | scalar/object |
| 列表与property | getInstalledPackages、queryProperty、getSystemAvailableFeatures | Computer/PackageProperty | sliced list |
| 安装/删除/数据 | deletePackageVersioned、clearApplicationUserData、freeStorage | PMS helpers/Installer | callback/IntentSender |
| 用户状态 | hidden、stopped、enabled、suspended、app-lock | PackageStateMutator/helpers | scalar/array |
| Preferred/domain | addPreferredActivity、cross-profile、domain backup | Preferred/Domain helpers | direct/byte[] |
| Dex/ART | notifyDexLoad、performDexOptMode、getArtManager | DexUseManager/DexOptHelper | oneway/scalar/child Binder |
| Instant/archive | instant resolver、getArchivedPackage、isAppArchivable | Computer/PackageArchiver | object/scalar |
| 校验/observer | verifyPendingInstall、checksums、package monitor | Handler/helper/callback | async callback |
| 模块/系统角色 | module info、required package names、APEX dirs | Module provider/PMS fields | scalar/list |
IPackageManagerBase 适合“一个 snapshot + 一个纯委托”或稳定 helper proxy;IPackageManagerImpl 适合调用方身份、feature flag、状态写入、Handler、锁、回调与多步骤事务。不是所有只读方法都必须在 Base,例如 queryProperty 还要构建自定义过滤 lambda,因此留在 Impl。
4.2 子服务Binder
部分方法返回另一个 Binder interface,而不是把整个子系统继续塞进 IPackageManager:
源码文件:frameworks/base/services/core/java/com/android/server/pm/IPackageManagerBase.java
@Override
@Deprecated
public final IPackageInstaller getPackageInstaller() {
// Return installer service for internal calls.
if (PackageManagerServiceUtils.isSystemOrRoot()) {
return mInstallerService;
}
final Computer snapshot = snapshot();
// Return null for InstantApps.
if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
Log.w(PackageManagerService.TAG,
"Returning null PackageInstaller for InstantApps");
return null;
}
return mInstallerService;
}system/root 不做 instant-app 过滤;普通调用者在确认不是 instant app 后,同样得到 mInstallerService Binder。后续 createSession/openSession 等身份与权限检查由 PackageInstallerService 自己执行。这里的边界只是阻止 instant app取得安装器子服务,并没有为每个调用 UID 创建包装 Binder。
getArtManager() 同样返回 IArtManager;permission 相关旧 IPackageManager 方法则在 Base 中转到 PermissionManager public service,说明方法保留在旧 AIDL不等于状态 owner 仍是 PMS。
5. 权限模型
5.1 AIDL注解
源码文件:frameworks/base/core/java/android/content/pm/IPackageManager.aidl
@EnforcePermission("MANAGE_USERS")
boolean setApplicationHiddenSettingAsUser(
String packageName, boolean hidden, int userId);
@EnforcePermission("CLEAR_APP_USER_DATA")
void clearApplicationUserData(
String packageName, IPackageDataObserver observer,
int userId, boolean restorePregrantedPermissions);
@PermissionManuallyEnforced
boolean setPackageAppLockEnabled(
String packageName, int userId, boolean enabled);@EnforcePermission 不应被描述成“generated Stub 自动在 onTransact 中完成全部检查”。Android 17 生成 <method>_enforcePermission() helper,手写 override 必须调用它。真实实现可以直接看到该调用:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@android.annotation.EnforcePermission(
android.Manifest.permission.CLEAR_APP_USER_DATA)
@Override
public void clearApplicationUserData(
final String packageName,
final IPackageDataObserver observer,
final int userId,
boolean restorePregrantedPermissions) {
clearApplicationUserData_enforcePermission();
// ... cross-user, visibility and protected-package checks ...
}@PermissionManuallyEnforced 则声明实现自行负责。App-lock 方法把 calling UID/PID传给 AppLockPackageHelper,helper结合 feature flag、身份和 policy 决定。新增方法若漏掉 generated helper 或 manual enforcement,会留下真实 Binder 攻击面。
5.2 多层检查
单个方法常同时执行:
- signature/privileged permission;
- userId 存在与跨用户权限;
- shell/user restriction;
- 调用方是否拥有目标 package;
- package visibility;
- protected/required/system package policy;
- feature flag 与参数值域。
通过第一层不代表可以操作任意用户或任意包。例如 setApplicationHiddenSettingAsUser() 有 MANAGE_USERS generated helper,随后仍执行 cross-user、device admin、android package、required-system-package、installed/visibility、SDK/static library 与 protected package 检查。
5.3 Identity切换
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
final int callingUid = Binder.getCallingUid();
final Computer snapshot = snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"setApplicationHiddenSetting for user " + userId);
// ... checks using original caller ...
final long callingId = Binder.clearCallingIdentity();
try {
final PackageStateInternal packageState =
snapshot.getPackageStateInternal(packageName);
// ... mutate system state and send broadcasts ...
} finally {
Binder.restoreCallingIdentity(callingId);
}clearCallingIdentity 前必须保存 callingUid并完成 caller-based权限/可见性判断。clear 后的内部 Binder 调用以 system_server 身份执行,避免下游误把外部 app 当系统组件;但过滤仍应使用保存的原始 UID。finally 保证异常时恢复 Binder thread identity,防止同一线程处理下一事务时继承 system identity。
6. 写操作
6.1 隐藏应用
setApplicationHiddenSettingAsUser() 是同步 Binder write:方法返回前 live PackageUserState 已改变、kill/广播已发起、restrictions 写入已调度,但磁盘不一定已提交。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
commitPackageStateMutation(null, packageName, packageState1 ->
packageState1.userState(userId).setHidden(hidden));
final Computer newSnapshot = snapshotComputer();
final PackageStateInternal newPackageState =
newSnapshot.getPackageStateInternal(packageName);
if (hidden) {
killApplication(packageName, newPackageState.getAppId(), userId,
"hiding pkg", ApplicationExitInfo.REASON_OTHER);
mBroadcastHelper.sendApplicationHiddenForUser(
packageName, newPackageState, userId,
/* packageSender= */ PackageManagerService.this);
} else {
mBroadcastHelper.sendPackageAddedForUser(
newSnapshot, packageName, newPackageState, userId,
false /* isArchived */, DataLoaderType.NONE,
mAppPredictionServicePackage);
}
scheduleWritePackageRestrictions(userId);
return true;写后重新取得 Computer snapshot,消费者不会继续使用 mutation 前 snapshot。状态 owner 是 PackageUserState;磁盘 owner 是 user package-restrictions;kill/broadcast是即时副作用。返回 true 只表示请求被接受并执行到调度点,不保证接收广播的进程已经处理完成。
6.2 清除数据
clearApplicationUserData() 的 Binder 方法本身不执行慢 I/O,而是在权限/可见性验证后投递 PMS Handler,通过 observer 返回完成状态。
if (snapshot.getPackageStateForInstalledAndFiltered(
packageName, callingUid, userId) == null) {
if (observer != null) {
mHandler.post(() -> {
try {
observer.onRemoveCompleted(packageName, false);
} catch (RemoteException e) {
Log.i(TAG, "Observer no longer exists.");
}
});
}
return;
}
if (mProtectedPackages.isPackageDataProtected(userId, packageName)) {
throw new SecurityException(
"Cannot clear data for a protected package: " + packageName);
}过滤后的包与不存在包使用同一 false callback,避免泄露跨用户/不可见 package。protected package 则对已知目标明确抛 SecurityException。
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
final boolean succeeded;
try (PackageFreezer freezer = freezePackage(packageName, userId,
"clearApplicationUserData",
ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */,
/* waitAppKilled= */ true)) {
try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
userId, restorePregrantedPermissions);
}
mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId);
synchronized (mLock) {
if (succeeded) {
resetComponentEnabledSettingsIfNeededLPw(packageName, userId,
callingUid);
}
}
}
if (succeeded) {
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm != null) {
dsm.checkMemory();
}
if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
== PERMISSION_GRANTED) {
final Computer snapshot = snapshotComputer();
unsuspendForSuspendingPackage(
snapshot, packageName, userId, USER_ALL);
removeAllDistractingPackageRestrictions(snapshot, userId);
synchronized (mLock) {
flushPackageRestrictionsAsUserInternalLocked(userId);
}
}
}
if (observer != null) {
try {
observer.onRemoveCompleted(packageName, succeeded);
} catch (RemoteException e) {
Log.i(TAG, "Observer no longer exists.");
}
}
}
});异步任务执行时重新 snapshot,先 freeze/等待进程退出,再持 mInstallLock 调 installd 清数据;callback 是结束路径。客户端 Binder 方法返回与 observer 回调之间可能相隔很久,客户端不能把 return 当成功。
7. Oneway通知
7.1 三个方法
源码文件:frameworks/base/core/java/android/content/pm/IPackageManager.aidl
oneway void notifyPackageUse(String packageName, int reason);
oneway void notifyDexLoad(String loadingPackageName,
in Map<String, String> classLoaderContextMap,
String loaderIsa);
oneway void registerDexModule(in String packageName,
in String dexModulePath, boolean isSharedModule,
IDexModuleRegisterCallback callback);oneway 的含义是客户端 transaction 不等待 reply,方法必须 void,服务端异常不能作为同步 exception 返回。它不会自动切换到 PMS Handler;transaction 仍由 system_server Binder thread pool调度,方法体若做慢工作仍会占用 Binder线程。
7.2 Usage通知
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void notifyPackageUse(String packageName, int reason) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
Computer snapshot = snapshotComputer();
final boolean notify;
if (snapshot.getInstantAppPackageName(callingUid) != null) {
notify = snapshot.isCallerSameApp(packageName, callingUid);
} else {
notify = !snapshot.isInstantAppInternal(
packageName, callingUserId, Process.SYSTEM_UID);
}
if (!notify) {
return;
}
notifyPackageUseInternal(packageName, reason);
}即使是无返回值通知,也不能任意探测或污染别的包 usage。instant app只能报告自己,普通调用者不能报告不可见 instant target。内部更新 PackageStateUnserialized usage数组,故意不使完整 snapshot失效,最终由 PackageUsage 独立持久化。
7.3 Dex通知
public void notifyDexLoad(String loadingPackageName,
Map<String, String> classLoaderContextMap,
String loaderIsa) {
int callingUid = Binder.getCallingUid();
Computer snapshot = snapshot();
if (!PackageManagerServiceUtils.isSystemOrRoot()
&& !snapshot.isCallerSameApp(
loadingPackageName, callingUid,
true /* resolveIsolatedUid */)) {
Slog.w(PackageManagerService.TAG,
"Invalid dex load report. loadingPackageName="
+ loadingPackageName + ", uid=" + callingUid);
return;
}
DexUseManagerLocal dexUseManager =
DexOptHelper.getDexUseManagerLocal();
try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
LocalManagerRegistry.getManager(PackageManagerLocal.class)
.withUnownedFilteredSnapshot(snapshot)) {
dexUseManager.notifyDexContainersLoaded(
filteredSnapshot, loadingPackageName,
classLoaderContextMap);
}
}非 system/root 只能为自己的 appId报告,isolated UID会解析回 owner。实现从 Binder Computer snapshot派生 PackageManagerLocal filtered snapshot,再交 ART DexUse manager。AIDL 的 loaderIsa 参数在当前实现没有继续使用,不能依据接口名假设它仍控制 dexopt。
registerDexModule() 在 Android U 以后被明确忽略,只异步 callback false并说明不支持;接口为了兼容仍保留,但不是有效注册路径。
8. 大列表传输
8.1 PackageInfoList
Android 17 的 getInstalledPackages() 返回 PackageInfoList,不是普通 List。
源码文件:frameworks/base/core/java/android/content/pm/PackageInfoList.java
/**
* A list of PackageInfos that can be parcelled efficiently, deduping common
* strings that are reused across packages and paginating a list that is too large.
*/
public final class PackageInfoList
extends ParceledListSlice<PackageInfo> {
@Override
protected ReadWriteHelper createReadWriteHelper() {
return new ParcelDedupHelper.Builder()
.dedupBinders(true)
.dedupString8(true)
.dedupString16(true)
.build();
}
}PackageInfo 重复大量 package/permission/process 字符串,专用 list在 ParceledListSlice 分页基础上增加 Binder/String8/String16 去重。客户端 ApplicationPackageManager 取 PackageInfoList.getList() 返回 public List。
8.2 Binder retriever
源码文件:frameworks/base/core/java/android/content/pm/BaseParceledListSlice.java
下面聚焦分页控制流;完整实现还在每批写入前验证所有元素类型一致,并安装/移除 ReadWriteHelper,这些安全边界由后文测试覆盖。
private static final int MAX_IPC_SIZE =
IBinder.getSuggestedMaxIpcSizeBytes();
public void writeToParcel(Parcel dest, int flags) {
final int N = mList.size();
final int callFlags = flags;
dest.writeInt(N);
if (N > 0) {
int i = 0;
while (i < N && i < mInlineCountLimit
&& dest.dataSize() < MAX_IPC_SIZE) {
dest.writeInt(1);
writeElement(mList.get(i), dest, flags);
i++;
}
if (i < N) {
dest.writeInt(0);
Binder retriever = new Binder() {
@Override
protected boolean onTransact(
int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int i = data.readInt();
reply.writeNoException();
while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
reply.writeInt(1);
writeElement(mList.get(i), reply, callFlags);
i++;
}
if (i < N) {
reply.writeInt(0);
} else {
mList = null;
}
return true;
}
};
dest.writeStrongBinder(retriever);
}
}
}它不是共享内存。初始 reply 内联尽可能多元素,再附带匿名 Binder retriever;客户端反复 transact(index) 拉取后续批次。每个额外 round-trip 都可能失败,客户端若只收到部分列表会抛 BadParcelableException。
单个 Parcelable 仍可能大于建议阈值,BaseParceledListSlice只 warning,无法把一个元素再拆开。调用方请求 GET_PERMISSIONS、GET_META_DATA 等膨胀 flags时,单 PackageInfo 大对象仍是风险。
9. Callback接口
9.1 Observer结束路径
删除、清数据、释放空间、checksum、move、dex module 等方法使用 callback Binder。服务端必须处理 callback 已死亡:捕获 RemoteException后只记录日志,不能让完成线程崩溃。callback 是否执行、执行线程和 result语义由各 API 定义,不能从 AIDL return type推断。
9.2 PackageMonitor
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void registerPackageMonitorCallback(
@NonNull IRemoteCallback callback, int userId) {
int uid = Binder.getCallingUid();
int targetUserId = ActivityManager.handleIncomingUser(
Binder.getCallingPid(), uid, userId,
true, true, "registerPackageMonitorCallback",
mContext.getPackageName());
mPackageMonitorCallbackHelper.registerPackageMonitorCallback(
callback, targetUserId, uid);
}helper 用 RemoteCallbackList 保存 callback,并把注册 userId/UID作为 cookie。通知时按目标用户、broadcast allowlist和 filtered extras筛选,再在 Handler上调用 sendResult()。RemoteCallbackList 自动跟踪 Binder death;用户删除时还主动注销该用户 callback。
callback 通道主要降低 PackageMonitor 等待广播的延迟,但不会绕过包可见性。filterExtras 返回 null时,helper完全跳过该 callback。
10. 失败语义
10.1 不可见与不存在
包查询通常把不可见目标伪装成不存在:null、空列表、INVALID_UID或 NameNotFoundException。跨用户 app enumeration 测试要求“安装在另一个用户前后得到相同错误”,防止错误信息成为侧信道。
写 API 的选择更细:clear data 对不可见包 callback false;hidden API 对目标不存在/未安装/过滤返回 false;签名/keyset 某些方法抛 IllegalArgumentException;权限本身不足抛 SecurityException。新增 API 必须选择不会泄露额外信息的失败形态。
10.2 同步异常
普通同步 Binder 方法可以把 SecurityException、IllegalArgumentException、ParcelableException或服务端声明异常写入 reply;ApplicationPackageManager 再转换成 public checked/runtime API。RemoteException 通常表示 Binder/system_server故障,而不是业务 NameNotFound。
oneway 没有 reply,客户端看不到服务端业务异常。实现应验证后静默丢弃或记录日志,不应依赖客户端 catch。
10.3 中途变化
Base 每次调用拿一个 snapshot;复杂 Impl 可能在 Handler异步执行时重新 snapshot。clear data 就在 Binder thread验证一次、Handler执行前再次读取,避免长延迟后使用陈旧 PackageSetting。若业务要求“验证与写入原子”,应使用 PackageStateMutator initial state或在锁内重验,不能持有旧 Computer跨异步边界。
11. 调试入口
以下命令只读 Binder 服务与调用统计。输入是当前设备,输出用于确认服务发布和 transaction activity;不同 build 的 dumpsys 字段可能不同。
# 输入:servicemanager 的 Java package 服务。
# 输出:package Binder 是否已发布;package_native 是另一接口。
adb shell service list \
| rg '(^|[[:space:]])package(_native)?:'
# 输入:PMS dumpsys。
# 输出:Settings、resolver、shared user、snapshot 等服务端状态。
adb shell dumpsys package
# 输入:Binder transaction 统计(需设备支持对应 debugfs/binderfs 节点和权限)。
# 输出:system_server Binder 线程与 transaction 计数,不包含方法名级 trace。
adb shell cat /dev/binderfs/binder_logs/stats 2>/dev/null \
| head -n 80定位具体方法耗时时,优先看 Perfetto binder transaction 与 PMS 自己的 trace slice,例如 Base 的 queryIntentActivities 显式打 TRACE_TAG_PACKAGE_MANAGER。只有 transaction code 时,可用同一 build 生成的 AIDL Stub 映射;transaction code不是跨版本稳定编号。
12. 测试证据
12.1 可见性测试
源码文件:frameworks/base/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@Test
public void testGetSigningKeySet_cannotDetectCrossUserPkg() {
final IllegalArgumentException e1 = assertThrows(
IllegalArgumentException.class,
() -> mIPackageManager.getSigningKeySet(
CROSS_USER_TEST_PACKAGE_NAME));
installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
final IllegalArgumentException e2 = assertThrows(
IllegalArgumentException.class,
() -> mIPackageManager.getSigningKeySet(
CROSS_USER_TEST_PACKAGE_NAME));
assertThat(e1.getMessage()).isEqualTo(e2.getMessage());
}输入是在另一个用户安装目标包;动作通过 AppGlobals.getPackageManager() 直接调用 IPackageManager;断言安装前后 exception类型和消息完全相同。它证明 Binder API不泄露跨用户包是否存在,但不覆盖同用户 <queries> package visibility。
同测试类还验证 cross-user userId 调用抛 SecurityException、getUidForSharedUser() 在目标只安装于其他用户时始终 INVALID_UID、clearApplicationUserData始终 callback false。它覆盖多种失败返回协议,而不是只测试 getPackageInfo null。
12.2 分页测试
源码文件:frameworks/base/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
public void testLargeList() throws Exception {
final int thresholdBytes = 256 * 1024;
final int objectCount = thresholdBytes / measureLargeObject();
List<LargeObject> list = new ArrayList<>();
for (int i = 0; i < objectCount; i++) {
list.add(new LargeObject(
i * 5, (i * 5) + 1, (i * 5) + 2,
(i * 5) + 3, (i * 5) + 4));
}
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(new ParceledListSlice<>(list), 0);
parcel.setDataPosition(0);
ParceledListSlice<LargeObject> slice =
parcel.readParcelable(getClass().getClassLoader());
assertEquals(objectCount, slice.getList().size());
}测试构造超过 256KB 的列表,迫使 retriever 分批,再逐元素核对字段。testThrownException() 把 inline limit设为 1,让后续 retriever序列化某元素时抛异常,客户端必须收到 ServiceSpecificException;testHomogeneousElements() 构造异构恶意列表,读取端必须拒绝不同 Parcelable 类型。
12.3 PackageInfo去重
源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInfoListTest.java
@Test
public void testParcelWithSameStrings() {
List<PackageInfo> rawList = new ArrayList<>();
String[] permissionStrings = generatePermissionStrings(10);
PackageInfo pi1 = generatePackageInfoWithPermissionStrings(
"com.foo1", permissionStrings);
PackageInfo pi2 = generatePackageInfoWithPermissionStrings(
"com.foo2", permissionStrings);
rawList.add(pi1);
rawList.add(pi2);
PackageInfoList packageInfoList = new PackageInfoList(rawList);
PackageInfoList parcelled = parcelAndUnparcel(packageInfoList);
assertPackageInfoListEquals(packageInfoList, parcelled);
}两个 PackageInfo复用同一组 permission strings,round-trip 后逐项比较 package与权限名;另一个测试使用不同 strings。它验证 ParcelDedupHelper 不改变语义,不直接测量节省字节数或跨进程分页。
12.4 异步实现测试
PackageManagerServiceVerificationTimeoutTest 使用极简 PMS 创建真实 mPms.new IPackageManagerImpl(),调用 extendVerificationTimeout() 后 capture Handler Runnable。pending verification不存在时,运行 Runnable不得创建 response message;同 verifier重复延长且 flag关闭时,只允许一次 delayed message。它验证 Binder facade到 Handler 的异步边界和调用 UID保留,不经过 Binder driver/Parcel。
13. 源码推演
给定“getPackageInfo() 在服务端能查到 PackageSetting,但应用收到 NameNotFoundException”,应按顺序检查 framework PropertyInvalidatedCache、目标 user是否存在、cross-user权限、shared-library filtering、AppsFilter visibility和 PackageUserState availability。不要只在 Settings map中搜索包名。
给定“调用 clearApplicationUserData 后方法立即返回”,应寻找 IPackageDataObserver callback,而不是认为数据已经删除。真正结束点在 PMS Handler中完成 freeze、mInstallLock、installd调用和 metadata清理后。
给定“oneway notifyDexLoad 偶发耗尽 Binder线程”,不能用“oneway 是后台线程”解释。oneway只让客户端不等待;服务端代码仍在 Binder pool执行。应检查方法体、DexUseManager调用和 Binder pool trace,必要时由实现显式投递自己的 executor。
给定“getInstalledPackages 结果很大却没有 TransactionTooLargeException”,应沿 PackageInfoList→ParceledListSlice→BaseParceledListSlice retriever理解多次 transaction;它不是一次 1MB reply,也不是共享内存。单个 PackageInfo过大仍可能突破建议阈值。
新增 IPackageManager 方法时,至少要回答:public PackageManager是否需要 facade/cache;Base能否用单 snapshot纯委托;AIDL权限 helper是否显式调用;跨用户和包可见性如何处理;是否需要 clearCallingIdentity;慢操作是否要 Handler/callback;返回集合如何分页;状态修改后写 global Settings还是 user restrictions。任何一项遗漏都会在安全、并发或重启恢复中留下缺口。
