Skip to content

Computer快照

拆解 PMS 快照的复制边界、失效版本、惰性重建、锁顺序和 live computer 语义。

基于android-17.0.0_r1
AndroidPackageManagerServiceComputerSnapshot并发源码阅读

Computer快照 ​

PMS 的 Computer 查询对象并不是每次调用都重新复制一份数据。Android 17 在 PackageManagerService 中维护一个可复用的 sSnapshot,用一个待发布版本号标记它是否过期;查询线程只有在版本不匹配时才进入重建路径。与此同时,持有 PMS 锁的代码可能得到 ComputerLocked,它读取 live state,而普通无锁读取使用 ComputerEngine 的快照副本。

本篇关注“快照如何被选择和发布”,不重复 PMS009 已经拆过的 Intent 候选和应用列表算法,也不提前展开 PMS011 的 PackageManagerLocal API。读完应能解释这些具体现象:

  • 为什么快照字段只复制部分 PMS 状态,权限服务和 UserManager 却保留外部引用;
  • sSnapshotPendingVersion 如何由 Watchable 变化和显式 invalidation 推进;
  • 多个线程同时发现过期时,为什么只允许一个线程重建;
  • mSnapshotLock -> mLock 的锁顺序如何避免重复构建和死锁风险;
  • ComputerLocked 为什么不是普通快照的“更快版本”;
  • use()、SnapshotStatistics 和缓存失效事件能证明什么,不能证明什么。

1. 两种视图 ​

1.1 Snapshot定义 ​

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

java
/**
 * A Snapshot is a subset of PackageManagerService state.  A snapshot is either live
 * or snapped.  Live snapshots directly reference PackageManagerService attributes.
 * Snapped snapshots contain deep copies of the attributes.
 */
class Snapshot {
    public static final int LIVE = 1;
    public static final int SNAPPED = 2;

    public final Settings settings;
    public final WatchedSparseIntArray isolatedOwners;
    public final WatchedArrayMap<String, AndroidPackage> packages;
    public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation;
    public final WatchedSparseBooleanArray webInstantAppsDisabled;
    public final ComponentName resolveComponentName;
    public final ActivityInfo resolveActivity;
    public final ActivityInfo instantAppInstallerActivity;
    public final ResolveInfo instantAppInstallerInfo;
    public final InstantAppRegistry instantAppRegistry;
    public final ApplicationInfo androidApplication;
    public final String appPredictionServicePackage;
    public final AppsFilterSnapshot appsFilter;
    public final ComponentResolverApi componentResolver;
    public final PackageManagerService service;
    public final WatchedArrayMap<String, Integer> frozenPackages;
    public final SharedLibrariesRead sharedLibraries;

注释直接给出了快照的两种语义:

  • LIVE:字段直接指向 PMS 当前属性,调用者必须处在允许读取 live state 的锁语境中;
  • SNAPPED:字段来自快照缓存或对象复制,供并发只读查询使用。

这不是“所有字段都深复制”的模型。Snapshot 是 PMS 状态的子集,字段是否复制由数据是否受 mLock 管理、是否实现 Snappable、以及读取期间能否保持稳定共同决定。

1.2 PMS 持有哪些对象 ​

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

java
// Compute read-only functions, based on live data.  This attribute may be modified multiple
// times during the PackageManagerService constructor but it should not be modified thereafter.
private ComputerLocked mLiveComputer;

private static final AtomicReference<Computer> sSnapshot = new AtomicReference<>();

// If this differs from Computer#getVersion, the snapshot is invalid (stale).
private static final AtomicInteger sSnapshotPendingVersion = new AtomicInteger(1);

/**
 * This lock is used to make reads from {@link #sSnapshotPendingVersion} and
 * {@link #sSnapshot} atomic inside {@code snapshotComputer()} when the versions mismatch.
 * This lock is not meant to be used outside that method. This lock must be taken before
 * {@link #mLock} is taken.
 */
private final Object mSnapshotLock = new Object();

四个成员承担不同责任:

成员owner可见性/用途
mLiveComputerPMS构造期间和持锁路径使用的 live view
sSnapshotPMS 静态缓存当前已发布的 ComputerEngine
sSnapshotPendingVersionPMS 静态版本最新失效版本;不匹配即过期
mSnapshotLockPMS只协调过期快照的检查和重建

mSnapshotLock 不是全局包状态锁,也不应该被其他路径随意持有。源码注释明确规定它必须先于 mLock 获取;后文会看到非持锁线程正是按这个顺序进入重建临界区。

1.3 Engine字段 ​

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

java
private final int mVersion;
private int mUsed = 0;

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;

private final Context mContext;
private final UserManagerService mUserManager;
private final PermissionManagerServiceInternal mPermissionManager;
private final ApexManager mApexManager;
private final PackageManagerServiceInjector mInjector;
private final ComponentResolverApi mComponentResolver;

快照字段和外部引用混在同一个 ComputerEngine 中,容易造成“全部都是 immutable copy”的误解。源码构造函数把二者明确分开:

  • Settings、包 map、isolated owner、instrumentation、instant app registry、AppsFilter、resolver 等属于查询视图;
  • Context、UserManagerService、PermissionManagerServiceInternal、ApexManager、injector 等直接引用 PMS 或其他服务。

因此,快照隔离只保证第一组数据的版本一致。第二组服务仍由它们自己的锁、Binder identity 和并发合同负责。一个查询如果在同一调用里同时访问两组对象,不能只凭 Computer 类型就断言全链路都是同一时刻的数据。

2. 数据复制 ​

2.1 Watchable数据 ​

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

java
// Keys are String (package name), values are Package.
@Watched
@GuardedBy("mLock")
final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>();
private final SnapshotCache<WatchedArrayMap<String, AndroidPackage>> mPackagesSnapshot =
        new SnapshotCache.Auto(mPackages, mPackages, "PackageManagerService.mPackages");

// Keys are isolated uids and values are the uid of the application
// that created the isolated process.
@Watched
@GuardedBy("mLock")
final WatchedSparseIntArray mIsolatedOwners = new WatchedSparseIntArray();
private final SnapshotCache<WatchedSparseIntArray> mIsolatedOwnersSnapshot =
        new SnapshotCache.Auto(mIsolatedOwners, mIsolatedOwners,
                "PackageManagerService.mIsolatedOwners");

@GuardedBy("mLock")
final WatchedArrayMap<String, Integer> mFrozenPackages = new WatchedArrayMap<>();
private final SnapshotCache<WatchedArrayMap<String, Integer>> mFrozenPackagesSnapshot =
        new SnapshotCache.Auto(mFrozenPackages, mFrozenPackages,
                "PackageManagerService.mFrozenPackages");

每一个 SnapshotCache.Auto 都绑定一个 source 和 watcher:source 发生变化时,会向 PMS 的 watcher 传播;构造 SNAPPED 时从 cache 取得副本。mPackages、mIsolatedOwners 和 mFrozenPackages 都由 mLock 保护,因而它们的 snapshot 必须在同一写锁语境下取得,不能在 map 修改过程中复制。

2.2 Watcher 注册 ​

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

java
private final Watcher mWatcher = new Watcher() {
    @Override
    public void onChange(@Nullable Watchable what) {
        PackageManagerService.onChange(what);
    }
};

// Link watchables to the class
@SuppressWarnings("GuardedBy")
private void registerObservers(boolean verify) {
    if (mPackages != null) {
        mPackages.registerObserver(mWatcher);
    }
    if (mSharedLibraries != null) {
        mSharedLibraries.registerObserver(mWatcher);
    }
    if (mInstrumentation != null) {
        mInstrumentation.registerObserver(mWatcher);
    }
    if (mWebInstantAppsDisabled != null) {
        mWebInstantAppsDisabled.registerObserver(mWatcher);
    }
    if (mAppsFilter != null) {
        mAppsFilter.registerObserver(mWatcher);
    }
    if (mInstantAppRegistry != null) {
        mInstantAppRegistry.registerObserver(mWatcher);
    }
    if (mSettings != null) {
        mSettings.registerObserver(mWatcher);
    }
    if (mIsolatedOwners != null) {
        mIsolatedOwners.registerObserver(mWatcher);
    }
    if (mComponentResolver != null) {
        mComponentResolver.registerObserver(mWatcher);
    }
    if (mFrozenPackages != null) {
        mFrozenPackages.registerObserver(mWatcher);
    }
}

Watcher 链路把多个数据 owner 汇聚到一个静态失效入口:包 map、Settings、resolver、AppsFilter、共享库和 frozen package 任一变化,都可以推进 sSnapshotPendingVersion。这并不意味着每次 onChange() 都立刻创建新快照;它只表示“已发布快照不再保证覆盖最新状态”。重建延迟到下一个需要 Computer 的调用。

2.3 SNAPPED复制 ​

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

java
Snapshot(int type) {
    if (type == Snapshot.SNAPPED) {
        settings = mSettings.snapshot();
        isolatedOwners = mIsolatedOwnersSnapshot.snapshot();
        packages = mPackagesSnapshot.snapshot();
        instrumentation = mInstrumentationSnapshot.snapshot();
        resolveComponentName = mResolveComponentName == null
                ? null : mResolveComponentName.clone();
        resolveActivity = new ActivityInfo(mResolveActivity);
        instantAppInstallerActivity = (mInstantAppInstallerActivity == null)
                ? null : new ActivityInfo(mInstantAppInstallerActivity);
        instantAppInstallerInfo = new ResolveInfo(mInstantAppInstallerInfo);
        webInstantAppsDisabled = mWebInstantAppsDisabled.snapshot();
        instantAppRegistry = mInstantAppRegistry.snapshot();
        androidApplication = (mAndroidApplication == null)
                ? null : new ApplicationInfo(mAndroidApplication);
        appPredictionServicePackage = mAppPredictionServicePackage;
        appsFilter = mAppsFilter.snapshot();
        componentResolver = mComponentResolver.snapshot();
        frozenPackages = mFrozenPackagesSnapshot.snapshot();
        sharedLibraries = mSharedLibraries.snapshot();
    }

复制方式按对象类型不同:

  • Watched* 结构调用自己的 snapshot();
  • ComponentName 使用 clone();
  • ActivityInfo、ResolveInfo、ApplicationInfo 使用复制构造函数;
  • String 直接复用,因为字符串不可变;
  • resolver、AppsFilter、Settings、shared libraries 交给各自的 snapshot 实现。

这段代码还说明了“快照的一致性”依赖 owner 的实现质量。如果某个新字段被加入 Snapshot,却只复制了顶层引用而没有复制内部可变集合,查询就会把 live mutation 暴露给无锁读线程。因此新增字段必须同时检查 Snappable、watcher 注册和 ComputerEngine 构造函数。

2.4 LIVE引用 ​

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

java
} else if (type == Snapshot.LIVE) {
    settings = mSettings;
    isolatedOwners = mIsolatedOwners;
    packages = mPackages;
    instrumentation = mInstrumentation;
    resolveComponentName = mResolveComponentName;
    resolveActivity = mResolveActivity;
    instantAppInstallerActivity = mInstantAppInstallerActivity;
    instantAppInstallerInfo = mInstantAppInstallerInfo;
    webInstantAppsDisabled = mWebInstantAppsDisabled;
    instantAppRegistry = mInstantAppRegistry;
    androidApplication = mAndroidApplication;
    appPredictionServicePackage = mAppPredictionServicePackage;
    appsFilter = mAppsFilter;
    componentResolver = mComponentResolver;
    frozenPackages = mFrozenPackages;
    sharedLibraries = mSharedLibraries;
} else {
    throw new IllegalArgumentException();
}
service = PackageManagerService.this;

LIVE 分支没有 clone 或 snapshot 调用,所有字段直接引用 PMS 成员。它只能在调用方已经获得正确锁、并且允许读取 live state 时使用。若把 ComputerLocked 当成普通 Computer 传给无锁后台线程,读到的集合可能与正在进行的写入交错,破坏 Computer 接口要求的只读稳定性。

3. 失效版本 ​

3.1 显式失效 ​

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

java
/**
 * This method is called when the state of PackageManagerService changes so as to
 * invalidate the current snapshot.
 * @param what The {@link Watchable} that reported the change
 * @hide
 */
public static void onChange(@Nullable Watchable what) {
    if (TRACE_SNAPSHOTS) {
        Log.i(TAG, "snapshot: onChange(" + what + ")");
    }
    sSnapshotPendingVersion.incrementAndGet();
}

/**
 * Report a locally-detected change to observers.  The <what> parameter is left null,
 * but it signifies that the change was detected by PackageManagerService itself.
 */
static void onChanged() {
    onChange(null);
}

onChange(what) 只做日志和版本递增。它不持有 mLock,不复制 map,也不替换 sSnapshot。onChanged() 是 PMS 自己检测到变化时使用的无来源版本,what == null 只是“来源未知/本地检测”的标记。

3.2 缓存失效 ​

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

java
/** Invalidate the package info cache, which includes updating the cached computer. */
public static void invalidatePackageInfoCache(int invalidationReason) {
    PackageManager.invalidatePackageInfoCache();
    onChanged();
    PackageMetrics.reportCacheInvalidationEvent(
            PackageMetrics.CACHE_TYPE_APPLICATION_AND_PACKAGE_INFO, invalidationReason);
}

/** Invalidate the get packages for UID cache, which includes updating the cached computer. */
public static void invalidateGetPackagesForUidCache(int invalidationReason) {
    ApplicationPackageManager.invalidateGetPackagesForUidCache();
    PackageMetrics.reportCacheInvalidationEvent(
            PackageMetrics.CACHE_TYPE_GET_PACKAGES_FOR_UID, invalidationReason);
}

两个方法的细节不同:

  • invalidatePackageInfoCache() 直接调用 onChanged(),因此会推进 sSnapshotPendingVersion;
  • invalidateGetPackagesForUidCache() 只清理客户端 UID 缓存并记录 metrics,源码中没有调用 onChanged()。

不能把方法名里的 “updating the cached computer” 当成每个缓存失效函数都同步重建 Computer 的证据。真正是否推进 snapshot 版本,要看方法体。

3.3 构造失效 ​

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

java
synchronized (mLock) {
    // Create the computer as soon as the state objects have been installed.  The
    // cached computer is the same as the live computer until the end of the
    // constructor, at which time the invalidation method updates it.
    mSnapshotStatistics = new SnapshotStatistics();
    sSnapshotPendingVersion.incrementAndGet();
    mLiveComputer = createLiveComputer();
    registerObservers(true);
}

Android 17 的构造流程中,mLiveComputer 可能在 PMS 字段逐步初始化时被重新创建。构造结束阶段显式递增 pending version,并重新建立 live view,再注册 watcher。这是启动初始化的特殊边界,不能简单套用运行时“任一写入都会通过 watcher 失效”的模型:某些字段在 observer 注册前已经完成初始化,必须由构造代码显式推进版本。

4. 选择与重建 ​

4.1 入口的第一次检查 ​

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

java
/**
 * Return the cached computer.  The method will rebuild the cached computer if necessary.
 * The live computer will be returned if snapshots are disabled.
 */
@VisibleForTesting(visibility = Visibility.PACKAGE)
@NonNull
public Computer snapshotComputer() {
    return snapshotComputer(true /*allowLiveComputer*/);
}

@Deprecated
@NonNull
public Computer snapshotComputer(boolean allowLiveComputer) {
    var isHoldingPackageLock = Thread.holdsLock(mLock);
    if (allowLiveComputer) {
        if (isHoldingPackageLock) {
            // If the current thread holds mLock then it may have modified state but not
            // yet invalidated the snapshot.  Always give the thread the live computer.
            return mLiveComputer;
        }
    }

    var oldSnapshot = sSnapshot.get();
    var pendingVersion = sSnapshotPendingVersion.get();

    if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
        return oldSnapshot.use();
    }

第一次检查按顺序做三件事:

  1. 判断当前线程是否持有 PMS mLock;
  2. 若允许 live computer 且持锁,直接返回 mLiveComputer;
  3. 否则读取已发布 snapshot 和 pending version,版本相等就复用。

注意:snapshotComputer() 默认 allowLiveComputer = true,而 PackageManagerLocal.snapshot() 的内部路径会使用不允许 live 的形式,以保证 Local API 总是拿到时间点快照。这个差异是 PMS011 的主题,但在本篇中要记住“同名方法有不同允许范围”。

4.2 持锁线程的重建 ​

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

java
if (isHoldingPackageLock) {
    // If the current thread holds mLock then it already has exclusive write access to the
    // two snapshot fields, and we can just go ahead and rebuild the snapshot.
    @SuppressWarnings("GuardedBy")
    var newSnapshot = rebuildSnapshot(oldSnapshot, pendingVersion);
    sSnapshot.set(newSnapshot);
    return newSnapshot.use();
}

持有 mLock 的线程不再获取 mSnapshotLock。因为它已经拥有 snapshot 两个字段所需的排他访问权,可以直接调用被 @GuardedBy("mLock") 标注的 rebuildSnapshot(),构建完成后原子替换 sSnapshot。

这条分支的意义不是“锁内查询更准确”这么简单:持锁线程可能刚刚修改了 live state,而 watcher 尚未把 snapshot 版本推进,返回旧 snapshot 会看不到自己的修改。因此它必须使用 live computer 或在锁内重建与当前状态匹配的新 snapshot。

4.3 双重检查 ​

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

java
synchronized (mSnapshotLock) {
    // Re-capture pending version in case a new invalidation occurred since last check
    var rebuildSnapshot = sSnapshot.get();
    var rebuildVersion = sSnapshotPendingVersion.get();

    // Check the versions again while the lock is held, in case the rebuild time caused
    // multiple threads to wait on the snapshot lock. When the first thread finishes
    // a rebuild, the snapshot is now valid and the other waiting threads can use it
    // without kicking off their own rebuilds.
    if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
        return rebuildSnapshot.use();
    }

    synchronized (mLock) {
        // Fetch version one last time to ensure that the rebuilt snapshot matches
        // the latest invalidation, which could have come in between entering the
        // SnapshotLock and mLock sync blocks.
        rebuildSnapshot = sSnapshot.get();
        rebuildVersion = sSnapshotPendingVersion.get();
        if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
            return rebuildSnapshot.use();
        }

        // Build the snapshot for this version
        var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
        sSnapshot.set(newSnapshot);
        return newSnapshot.use();
    }
}

这是一个“外层 snapshot lock + 内层 PMS lock + 两次检查”的协议:

  • 第一次检查防止等待线程在前一个线程已经完成重建后再次构建;
  • 进入 mLock 后再次读取版本,防止在等待 mSnapshotLock 或获取 mLock 期间发生新的 invalidation;
  • 只有最后一次检查仍不匹配,才按最新 rebuildVersion 建立快照。

4.4 锁顺序 ​

源码注释已经规定 mSnapshotLock must be taken before mLock。非持锁线程严格遵循这一顺序;持锁线程不再获取 mSnapshotLock,所以不会出现“先持有 mLock 再等待 snapshot lock”的反向路径。

这个设计仍要求其他代码遵守 PMS 全局锁层次。如果另一个线程先拿 mLock,再等待 mSnapshotLock,就可能与正在 mSnapshotLock -> mLock 的重建线程形成循环等待。因而 mSnapshotLock 不能被业务 helper 用作普通同步锁,锁顺序是快照实现的一部分。

5. 重建内容 ​

5.1 重建锁与版本 ​

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

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

rebuildSnapshot() 的输入是旧快照和目标版本,输出是新 ComputerEngine。构建动作在 mLock 下执行,因此 Snapshot(SNAPPED) 读取的 Settings、map、resolver 和 watcher source 不会与 PMS 写入交错。oldSnapshot 不用于增量复制;它只用于统计上一个快照被复用多少次。

5.2 原子发布 ​

sSnapshot.set(newSnapshot) 只替换 PMS 后续 lookup 的入口,不会让已经拿到旧 Computer 的查询立刻失效。旧查询仍持有旧 ComputerEngine 引用,并继续读取旧版本数据;新调用从 sSnapshot 拿到新版本。

这是一种读者友好的“版本切换”:查询不需要在每次字段访问时重新检查 pending version,但调用方也不能把多个独立查询返回的对象拼成一个假定一致的结果。要保持业务级一致性,应在业务开始时拿一个 Computer 并复用到结束。

5.3 重建中失效 ​

重建线程进入 mLock 后,其他写线程不能修改受锁保护的 snapshot source;因此正在构建的 Snapshot(SNAPPED) 与当时 live state 一致。若某个不受 mLock 管理的外部服务在构建期间变化,ComputerEngine 仍持有该服务引用,不能把这些外部变化纳入 PMS 的 pending version 协议。

这也是 Computer 设计中“快照字段”和“外部 service 引用”必须分开理解的原因:PMS 快照只对它自己管理的状态提供版本边界。

6. Live Computer ​

6.1 创建与版本 ​

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

java
/** Create a live computer */
private ComputerLocked createLiveComputer() {
    return new ComputerLocked(new Snapshot(Snapshot.LIVE));
}

PMS 用这个工厂创建 live view;ComputerLocked 自身只负责继承查询实现并替换少量 live accessor。

java
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();
    }
}

live computer 的 version 是 -1,不是一个会参与 sSnapshotPendingVersion 匹配的正常快照版本。它继承 ComputerEngine 的查询算法,但通过 protected accessor 读取 PMS 当前的 resolver component、instant app installer 和 core Android application。

6.2 持锁与无锁 ​

两条路径的正确使用场景不同:

  • PMS 自己在 mLock 下完成写后读取时,live computer 能看到尚未发布到 snapshot 的最新状态;
  • Binder 查询或 system_server 后台只读路径通常不持锁,应该使用稳定的 ComputerEngine snapshot;
  • PackageManagerLocal 需要严格的时间点快照时,会禁止 live computer,即使调用线程偶然持有 PMS 锁。

6.3 Live使用边界 ​

ComputerLocked 的类名表示它面向 live computer,但类自身并没有在每个方法内部 synchronized (mLock)。锁由调用方和 snapshotComputer() 的选择路径保证。把一个 ComputerLocked 保存到字段后异步使用,并不会自动获得锁保护;它只是一个直接引用 PMS live 属性的对象。

7. 统计与诊断 ​

7.1 use计数 ​

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

java
/** Record that the snapshot was used. */
public final Computer use() {
    mUsed++;
    return this;
}

/** Return the usage counter. */
public final int getUsed() {
    return mUsed;
}

每次 snapshotComputer() 复用或发布快照时调用 use(),返回自身供调用方继续使用。mUsed 是管理统计,不是引用计数:它不会阻止旧快照被回收,也不会保证并发线程看到一个原子递增的精确全局计数。多个读线程的真实访问量不能仅凭这个普通 int 做严格计量。

7.2 统计输入 ​

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

java
/**
 * Record a rebuild.  Cumulative and current statistics are updated.  Events may be generated.
 * @param now The time at which the snapshot rebuild began, in ns.
 * @param done The time at which the snapshot rebuild completed, in ns.
 * @param hits The number of times the previous snapshot was used.
 * @param packageCount The number of packages on the device.
 */
public final void rebuild(long now, long done, int hits, int packageCount) {
    final int duration = (int) (done - now);
    boolean reportEvent = false;
    synchronized (mLock) {
        mPackageCount = packageCount;
        final int timeBin = mTimeBins.getBin(duration / 1000);
        final int useBin = mUseBins.getBin(hits);
        final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US;
        final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME;
        mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick);
        mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick);
        if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) {
            if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) {
                reportEvent = true;
            }
        }
    }
    // The IO to the logger is done outside the lock.
    if (reportEvent) {
        EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits);
    }
}

统计记录的是“本次重建耗时、上一个快照的使用次数、包数量”。其中 hits = -1 表示没有旧快照,是首次构建;quick = hits <= SNAPSHOT_SHORT_LIFETIME 用于判断旧快照是否短命。日志 I/O 放在统计锁外,避免慢日志阻塞其他统计更新。

7.3 诊断时的正确推断 ​

可以从统计得到:

  • snapshot rebuild 是否频繁;
  • 上一个快照在被替换前使用了多少次;
  • 构建耗时是否达到 big/reportable 阈值;
  • 当前快照复制涉及多少 package state。

不能从统计直接得到:

  • 每个查询是否都使用了同一版本;
  • 外部 PermissionManager 或 UserManager 数据是否与 snapshot 同时刻;
  • 某个旧 Computer 是否仍被业务线程持有;
  • 重建是否导致了业务级结果的一致性。

8. 真实调用场景 ​

8.1 持锁写后立即查询 ​

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

java
void setPackageStoppedState(@NonNull String packageName, boolean stopped, int userId) {
    final Computer snapshot = snapshotComputer();
    final PackageStateInternal packageState =
            snapshot.getPackageStateInternal(packageName);
    if (packageState == null) {
        throw new IllegalArgumentException("Unknown package: " + packageName);
    }
    synchronized (mLock) {
        setPackageStoppedStateLPw(packageName, stopped, userId);
    }
}

这类路径需要结合实际调用点继续追锁和 snapshot 生命周期。若代码在进入 synchronized (mLock) 后再次调用 snapshotComputer(),会拿 live computer;若在锁外先拿 snapshot,再进入写锁,前后两个查询可能处于不同视图。判断是否一致,不能只看方法名,必须把锁范围和 snapshotComputer() 调用位置一起画出来。

8.2 Internal代理 ​

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

java
@Override
@Deprecated
public final PackageInfo getPackageInfo(String packageName,
        @PackageManager.PackageInfoFlagsBits long flags, int filterCallingUid, int userId) {
    return snapshot().getPackageInfoInternal(packageName,
            PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId);
}

PackageManagerInternalBase.snapshot() 每次方法调用都从 PMS 选择一个 Computer。所以连续调用两个便利代理不自动共享快照;需要业务级一致性时,调用方应显式获取 Computer,再在同一对象上完成所有查询。

8.3 Local方向 ​

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

java
/**
 * This method should only ever be called from {@link PackageManagerLocal#snapshot()}.
 *
 * @param allowLiveComputer Whether to allow a live computer instance based on caller
 *                          {@link #mLock} hold state.
 */
@Deprecated
public Computer snapshotComputer(boolean allowLiveComputer) {

这段 Javadoc 表明一个架构迁移方向:PackageManagerLocal.snapshot() 不能接受 live computer,必须获得时间点快照。PMS010 只记录这个边界,不在本篇重复 PackageManagerLocal 的 API 结构;下一篇会专门解释为什么显式 snapshot-scoped API 能减少便利代理的版本歧义。

9. 失败与并发边界 ​

9.1 惰性重建 ​

onChange() 只推进 pending version。若没有任何代码调用 snapshotComputer(),sSnapshot 可以继续保存旧对象;下一次查询才触发重建。因而“数据已变化”和“新快照已发布”之间存在惰性窗口。

这个窗口是有意的:PMS 不需要为每一个细小 setter 都同步复制数千个 package state。代价是调用方必须知道自己拿到的是哪个 Computer,并且不能把旧对象当作实时 PMS。

9.2 重建期间再次失效 ​

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

非持锁线程在 mSnapshotLock 外第一次读取版本后,可能有新的 invalidation;进入 mSnapshotLock 后会重新捕获版本。即使如此,进入 mLock 前后仍可能再次失效,因此源码做第三次读取。最终构建目标是最后一次观察到的 rebuildVersion,而不是最初读到的 version。

如果构建完成后立刻又发生变化,新快照可能刚发布就再次过期。这不是重建算法错误,而是版本化惰性缓存的正常结果;下一次调用会继续重建。

9.3 新旧并存 ​

旧 ComputerEngine 在所有引用释放前都可以继续被读取。它不会因为 sSnapshot 替换而被修改,也不会收到“请刷新”的回调。业务如果需要看到新状态,必须重新调用 snapshotComputer();不能期待旧对象自动更新。

9.4 外部引用的边界 ​

ComputerEngine 直接持有 PermissionManagerServiceInternal、UserManagerService、ApexManager 等引用。PMS pending version 不覆盖这些服务的所有变化。如果查询结果同时依赖 mPackages 快照和外部权限服务,最多能保证 PMS 包数据的 snapshot 版本,不能自动保证权限服务也在相同时间点。

10. 测试与证明范围 ​

10.1 测试夹具 ​

测试文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceInternalTest.java

java
PackageManagerService packageManagerService =
        spy(new PackageManagerService(injector, mTestParams));
doReturn(mSnapshotComputer).when(packageManagerService).snapshotComputer();

mPackageManagerInternal = packageManagerService.new PackageManagerInternalImpl();

这个夹具把 PMS 的 snapshotComputer() 替换成 mock,因此适合验证 PackageManagerInternalImpl 调用哪一个 snapshot 方法、如何把参数传给 Computer,以及状态查询的异常/默认值。它不会运行真实的 sSnapshotPendingVersion、mSnapshotLock、Watchable observer 或重建竞争。

10.2 AppsFilter测试 ​

测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java

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,动作是改变可见性条件后调用过滤器,断言 true/false,并确认 watcher 没有收到变化。它证明 AppsFilter 查询是只读判断,不证明 PMS 什么时候重建 ComputerEngine。

10.3 快照测试 ​

要证明本篇的并发协议,测试至少需要覆盖:

  1. 复用:pending version 与 Computer#getVersion() 相等时,不创建新引擎,且 use() 增加一次。
  2. 单线程重建:版本不匹配时发布目标版本的 ComputerEngine。
  3. 并发双检:两个线程同时发现过期,只有第一个线程重建,第二个线程在 mSnapshotLock 内复用结果。
  4. 中途 invalidation:在第一次版本读取与最终获取 mLock 之间推进版本,最终构建使用新版本。
  5. 持锁 live:持有 mLock 且允许 live 时返回 mLiveComputer;禁止 live 时仍构建/复用 snapped computer。
  6. 旧对象稳定:替换 sSnapshot 后,已取得的旧对象仍保留旧版本且不被写入。

当前已找到的 PackageManagerServiceInternalTest 和 AppsFilterImplTest 没有覆盖这组完整证明,因此不能把它们的通过扩大为 snapshot 并发正确性的证明。真实验证需要专门的 PMS snapshot 测试或带可控锁屏障的集成测试。

11. 阅读路线 ​

遇到快照相关问题时,可以按以下顺序在源码中定位:

  1. 从 PackageManagerService.snapshotComputer() 开始,记录 allowLiveComputer、当前是否持有 mLock 和第一次版本比较。
  2. 顺着 sSnapshot、sSnapshotPendingVersion 和 mSnapshotLock 读完整双重检查,不要只看第一次 if。
  3. 进入 rebuildSnapshot(),查看 Snapshot(SNAPPED) 的每个字段是 snapshot()、clone、复制构造还是直接引用。
  4. 回到 PMS 字段声明,查每个 SnapshotCache.Auto 的 source、watcher 和 @GuardedBy 锁。
  5. 搜索 registerObservers()、onChange()、onChanged()、invalidatePackageInfoCache(),确认是什么动作推进版本。
  6. 搜索 ComputerLocked 的 protected accessor,确认 live view 到底只覆盖哪些字段。
  7. 最后把调用方的锁范围、业务需要的对象数量和旧对象是否跨线程保存画出来。

小结 ​

Android 17 PMS 快照机制的关键不在“复制一份 map”,而在版本、锁和复制边界共同构成的协议:

  • Snapshot.SNAPPED 复制受 PMS 管理的查询状态,Snapshot.LIVE 直接引用 PMS 当前对象;
  • Watchable observer 和显式 invalidation 只推进 sSnapshotPendingVersion,重建由下一次 snapshotComputer() 惰性触发;
  • 无锁线程通过 mSnapshotLock -> mLock 和多次版本检查,避免重复重建并尽量构建最新版本;
  • 持锁线程可以返回 mLiveComputer,但 ComputerLocked 自身不提供自动锁保护;
  • sSnapshot.set() 只影响后续获取,旧 ComputerEngine 可以继续服务已经开始的查询;
  • use() 和 SnapshotStatistics 记录缓存行为,不等于引用计数、全链路一致性或并发正确性的完整证明;
  • 快照只覆盖 PMS 自己管理的字段,PermissionManager、UserManager 等外部服务仍有独立时序。

下一篇将沿着 PackageManagerLocal 的显式 snapshot API 继续读,重点比较它如何约束 live computer、如何把 snapshot 生命周期交给调用方,以及它为什么是替代 PackageManagerInternal 便利代理的迁移方向。