Skip to content

PMS Binder接口

从 ApplicationPackageManager 到 IPackageManagerImpl,拆解 PMS 查询、写入、权限、oneway、回调和大列表传输的 Binder 边界。

基于android-17.0.0_r1
AndroidPackageManagerServiceIPackageManagerBinderAIDL源码阅读

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

java
/**
 * 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

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

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。可直接确认的手写继承链是:

text
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

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

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

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

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

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

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

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 调用两者相同。

java
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、getActivityInfoComputerobject/null
Intent/provider解析resolveIntent、queryIntentActivities、resolveContentProviderComputer/ResolveIntentHelperResolveInfo/list
UID/签名/keysetgetPackagesForUid、checkSignatures、getSigningKeySetComputer/KeySetManagerscalar/object
列表与propertygetInstalledPackages、queryProperty、getSystemAvailableFeaturesComputer/PackagePropertysliced list
安装/删除/数据deletePackageVersioned、clearApplicationUserData、freeStoragePMS helpers/Installercallback/IntentSender
用户状态hidden、stopped、enabled、suspended、app-lockPackageStateMutator/helpersscalar/array
Preferred/domainaddPreferredActivity、cross-profile、domain backupPreferred/Domain helpersdirect/byte[]
Dex/ARTnotifyDexLoad、performDexOptMode、getArtManagerDexUseManager/DexOptHelperoneway/scalar/child Binder
Instant/archiveinstant resolver、getArchivedPackage、isAppArchivableComputer/PackageArchiverobject/scalar
校验/obserververifyPendingInstall、checksums、package monitorHandler/helper/callbackasync callback
模块/系统角色module info、required package names、APEX dirsModule provider/PMS fieldsscalar/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

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

java
@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

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 多层检查 ​

单个方法常同时执行:

  1. signature/privileged permission;
  2. userId 存在与跨用户权限;
  3. shell/user restriction;
  4. 调用方是否拥有目标 package;
  5. package visibility;
  6. protected/required/system package policy;
  7. 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

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

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 返回完成状态。

java
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。

java
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

java
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

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通知 ​

java
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

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,这些安全边界由后文测试覆盖。

java
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

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 字段可能不同。

bash
# 输入: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

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

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

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。任何一项遗漏都会在安全、并发或重启恢复中留下缺口。