Skip to content

PackageStateMutator 事务提交

追踪 Android 17 包状态 mutation 的序列校验、wrapper 写入、watcher 通知和冲突重试。

基于android-17.0.0_r1
AndroidPMSPackageStateMutatorComputerConcurrency

PackageStateMutator 事务提交 ​

本文承接 PackageSetting 数据结构 和 PMS 并发控制,只讨论“读到 snapshot 后如何安全写回包状态”。目标不是重复列出 setter,而是解释 recordInitialState()、generateResult()、commitPackageStateMutation() 和 onFinished() 组成的提交契约。读完后,读者应能判断一次 mutation 是否因包集合变化或状态变化而失效,知道 consumer 何时运行,以及为什么成功返回仍不等于磁盘或外部服务已经完成同步。

1. 两条序列 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
  • frameworks/base/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java

PMS 用两条独立序列描述 optimistic mutation 的前提:mChangedPackagesTracker.getSequenceNumber() 表示包集合/版本层面的变化;PackageStateMutator.sStateChangeSequence 表示任意 PackageSetting 状态字段变化。调用方先记录二者,锁外完成查询和计算,再尝试提交。

java
public PackageStateMutator.InitialState recordInitialState() {
    return mPackageStateMutator.initialState(
            mChangedPackagesTracker.getSequenceNumber());
}

public static void onPackageStateChanged() {
    sStateChangeSequence.incrementAndGet();
}

两条序列不能合并成一个“版本号”:包可能被增删但目标 user state 没变,也可能包集合不变而 hidden/stopped 等字段改变。不同结果让调用方决定是重算、重试还是放弃。

2. 结果分类 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java;符号:generateResult、Result。

java
public Result generateResult(@Nullable InitialState state,
        int changedPackagesSequenceNumber) {
    if (state == null) {
        return Result.SUCCESS;
    }
    boolean packagesChanged = changedPackagesSequenceNumber
            != state.mPackageSequence;
    boolean stateChanged = sStateChangeSequence.get()
            != state.mStateSequence;
    if (packagesChanged && stateChanged) {
        return Result.PACKAGES_AND_STATE_CHANGED;
    } else if (packagesChanged) {
        return Result.PACKAGES_CHANGED;
    } else if (stateChanged) {
        return Result.STATE_CHANGED;
    }
    return Result.SUCCESS;
}

Result 的 isCommitted() 只对 SUCCESS 为 true;三个 changed 结果表示 consumer 尚未运行。它们不是异常,也不会自动重试。调用方必须依据业务重新取得 snapshot,并在新前提下再次执行 mutation。

3. 写锁与 consumer ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:两个 commitPackageStateMutation 重载。

java
public PackageStateMutator.Result commitPackageStateMutation(
        PackageStateMutator.InitialState initialState,
        Consumer<PackageStateMutator> consumer) {
    synchronized (mPackageStateWriteLock) {
        Result result = mPackageStateMutator.generateResult(
                initialState, mChangedPackagesTracker.getSequenceNumber());
        if (result != Result.SUCCESS) {
            return result;
        }
        consumer.accept(mPackageStateMutator);
        mPackageStateMutator.onFinished();
    }
    return Result.SUCCESS;
}

consumer 在 mPackageStateWriteLock 内执行;Android 17 生产 PMS 中该锁与 mLock 是同一对象。consumer 的职责应限制为轻量 setter:不能在其中做 Installer I/O、跨服务 Binder、网络或长时间计算,否则会把所有 PMS 查询和写路径堵在同一把锁上。

提交成功后 onFinished() 才通知本次触碰过的 PackageSetting。这一步触发 watchable/snapshot 变化;如果 consumer 修改了值但跳过 onFinished(),后续 Computer 可能继续复用旧快照。

4. 指定包 wrapper ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java;符号:forPackage、forPackageNullable、StateWriteWrapper。

java
@NonNull
public PackageStateWrite forPackage(String packageName) {
    return setState(mActiveStateFunction.apply(packageName));
}

@Nullable
public PackageStateWrite forPackageNullable(String packageName) {
    PackageSetting packageState = mActiveStateFunction.apply(packageName);
    setState(packageState);
    if (packageState == null) {
        return null;
    }
    return setState(packageState);
}

forPackage() 的返回类型非空,但 wrapper 内部 state 可以为 null;所有 setter 都会先检查 mState != null,因此目标包不存在时静默 no-op。需要明确检测不存在时,调用方使用 forPackageNullable() 或先从 Settings 取得 package state。

PMS 的按包重载会把 wrapper 交给 consumer:

java
PackageStateWrite state = mPackageStateMutator.forPackage(packageName);
if (state == null) {
    return PackageStateMutator.Result.SPECIFIC_PACKAGE_NULL;
}
consumer.accept(state);
state.onChanged();

阅读具体调用点时要以当前重载和 wrapper 实现为准,不能只凭方法名假定所有空包都会返回 SPECIFIC_PACKAGE_NULL。

5. user state 写入 ​

StateWriteWrapper.userState(userId) 将 PackageSetting 的 PackageUserStateImpl 包装成 PackageUserStateWrite。它可以修改 installed、hidden、stopped、suspended、overlay paths、harmful warning 等 per-user 字段,但仍受同一个 mutation 锁保护。

java
public PackageUserStateWrite userState(int userId) {
    var userState = mState == null ? null
            : mState.getOrCreateUserState(userId);
    if (userState != null) {
        userState.setWatchable(mState);
    }
    return mUserStateWrite.setStates(userState);
}

getOrCreateUserState 说明了一个重要副作用:即使此前没有 user 条目,写 wrapper 也可能创建 per-user 状态对象。调用方应在正确的 user 生命周期和写锁内使用它,不能把 wrapper 当作只读 view。

6. 重试调用方 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;相关符号:安装/组件状态 mutation 调用点。

需要 optimistic 校验的调用方通常按以下模式组织:

java
PackageStateMutator.InitialState initialState = recordInitialState();
Computer snapshot = snapshotComputer();
// 锁外依据 snapshot 计算目标状态
PackageStateMutator.Result result = commitPackageStateMutation(
        initialState, packageName, state ->
                state.userState(userId).setHidden(hidden));
if (!result.isCommitted()) {
    // 重新取得 snapshot,重新验证后重试或返回失败
}

如果在拿到 initial state 后另一个线程更新了任意包状态,STATE_CHANGED 会阻止旧 consumer 写入;如果包集合变化,PACKAGES_CHANGED 会阻止基于旧包列表的写入。initialState == null 则明确表示调用方放弃 optimistic 检查,直接在写锁内执行。

7. 通知传播 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java、frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java。

状态 setter 通过 watchable parent 向 PackageSetting.onChanged() 传播;PackageSetting 再触发 PackageStateMutator.onPackageStateChanged() 和 Settings/PMS watcher,最终使 snapshot cache 失效。这个通知链是“内存状态变更已对读者可见”的必要条件,和 packages.xml 延迟写盘是两件事。

通知传播只说明新的 snapshot 不应继续复用旧状态;它不表示权限、Intent resolver、AppsFilter、Installer 或外部服务已经完成各自的后续工作。那些消费者由具体业务 helper 继续调度。

8. 失败与恢复 ​

现象检查点解释
consumer 没有执行Result 是否为 SUCCESSchanged result 表示 optimistic 前提失效
不存在的包被“成功”写入使用了 forPackage() wrapper空 state setter 是 no-op;需要 nullable 入口
查询仍返回旧值onFinished()、PackageSetting watcher、snapshot 创建时点mutation 成功不等于旧 snapshot 自动更新
写入卡住 PMSconsumer 内是否做 I/O/Binderconsumer 持有 package state write lock
重试后仍冲突initial state 记录过早或重试未重读 snapshot每次重试都要建立新的前提
XML 没立即变化Settings 写盘调度state mutation 通知和持久化时机独立

9. 源码练习 ​

  1. 构造“先记录 initial state,再修改另一个包的 stopped 状态,最后提交目标包 hidden”的时序,判断返回 STATE_CHANGED 还是 PACKAGES_CHANGED。
  2. 对比 forPackage() 与 forPackageNullable(),说明为什么 Android 17 的非空 wrapper 不能证明目标包存在。
  3. 从 PackageUserStateWrite.setSuspended 追到 PackageSetting.onChanged(),标出 setter、watcher、sequence 和 snapshot invalidation 的顺序。
  4. 检查一个真实 PMS 调用点,判断 consumer 是否只做轻量状态写入,以及 changed result 是否有重试或失败返回路径。