Skip to content

PackageSetting

拆解 PackageSetting 的持久化身份、解析对象、临时派生状态、per-user 状态、mutation 与 sealed snapshot 边界。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageSettingPackageUserStatePackageState源码阅读

PackageSetting ​

本文面向已经读过 Settings持久化 的读者。前一篇说明 packages.xml 与 package-restrictions.xml 分别写什么;本文回到内存,回答这些字段最终落到哪个对象、为什么同一个包同时有 PackageSetting、AndroidPackage、PackageStateUnserialized 和多个 PackageUserStateImpl,以及查询线程为什么通常只接触只读 PackageStateInternal 或 sealed snapshot。

PackageSetting 不能简单理解为“一个包的配置 Bean”。它同时承担五种角色:Settings 中按包名索引的长期身份记录;扫描结果 AndroidPackageInternal 的设备状态外壳;每用户状态的 parent/watch owner;PackageStateInternal 只读接口的实现;snapshot 与 mutation 通知的根节点。不同字段的来源、持久化位置和失效时机并不相同。

读完本文,应能判断一个字段应该放在 AndroidPackage、PackageSetting 顶层、PackageStateUnserialized 还是 PackageUserStateImpl;能解释 pkg == null 为什么是合法状态;能沿一次 setHidden() 或 setMimeGroup() 追到 snapshot invalidation 与磁盘调度;也能区分普通 PackageSetting copy、sealed snapshot 和应用更新时的 Settings merge。

1. 记录契约 ​

1.1 类型边界 ​

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

java
/**
 * Settings data for a particular package we know about.
 *
 * @hide
 */
@DataClass(genGetters = true, genConstructor = false, genSetters = false, genBuilder = false)
@DataClass.Suppress({"getSnapshot", "getBooleans"})
public class PackageSetting extends SettingBase implements PackageStateInternal {

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

java
public interface PackageStateInternal extends PackageState {
    @Nullable
    AndroidPackageInternal getPkg();

    @NonNull
    PackageStateUnserialized getTransientState();

    @NonNull
    UUID getDomainSetId();

    @NonNull
    SparseArray<? extends PackageUserStateInternal> getUserStates();
}

PackageSetting 是可变实现类;PackageStateInternal 是 system_server 内部只读接口;PackageState 是面向 system_server API 消费者的 immutable 视图。查询代码应依赖接口,不应拿到 PackageSetting 后随意调用 setter。写操作由 PMS 锁、Settings helper 或 PackageStateMutator 约束。

@DataClass 生成大量 getter、equals/hashCode 或 setter,但关键构造、copy、snapshot 和状态修改方法仍是手写代码。阅读时不能只看文件前 300 行就认为没有后续字段访问逻辑,生成段同样属于真实实现。

1.2 所有权不变量 ​

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

java
/**
 * It is expected that all code that uses a {@link PackageSetting} understands this inner field
 * may be null. Note that this relationship only works one way. It should not be possible to
 * have an entry inside {@link PackageManagerService#mPackages} without a corresponding
 * {@link PackageSetting} inside {@link Settings#mPackages}.
 */
@Nullable
private AndroidPackageInternal pkg;

Settings 是 PackageSetting 的 owner:Settings#mPackages 可以保留一个没有可解析 APK 的 PackageSetting;反向则不允许,PMS 的 mPackages 中若有 AndroidPackage,Settings 必须有对应 PackageSetting。这个单向不变量解释了两张 map 的职责:

mapvalue能否在 APK 不可用时保留
Settings#mPackagesPackageSetting可以,保留 appId、签名、路径、用户数据等最小状态
PackageManagerService#mPackagesAndroidPackage不可以,只有本次成功解析的包

查询 ComputerEngine.getPackageStateInternal() 最终从 Settings snapshot 取 PackageSetting,而不是从 PMS mPackages 取解析对象:

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

java
public PackageStateInternal getPackageStateInternal(String packageName,
        int callingUid) {
    packageName = resolveInternalPackageNameInternalLocked(
            packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
    return mSettings.getPackage(packageName);
}

因此调用者拿到 PackageStateInternal 后仍必须检查 getAndroidPackage() 是否为 null。PackageSetting 存在只说明系统仍保留该包身份或数据,不保证 Manifest 已解析、组件可启动。

2. 状态分层 ​

PackageSetting 内部字段不能只按 Java 类型分类,更有用的方式是按“谁产生、谁消费、是否持久化”分层。

层代表字段来源持久化
身份层name、appId、sharedUserAppId、path、version、signaturesSettings 恢复或扫描注册packages.xml
包内容层pkgPackageParser2/扫描不直接持久化,每次启动重建
设备策略层flags、ABI、installSource、domainSetId、category、metadata扫描、安装与系统策略packages.xml
临时派生层seInfo、resolved libraries、updated-system 标志、usage扫描与运行期事件不进 packages.xml;usage 单独写文件
用户层installed、stopped、enabled、suspend、components、archive用户操作、安装/卸载、policypackage-restrictions.xml

同一个 getter 可能组合多层。例如 getPrimaryCpuAbi() 优先读取 PackageSetting 保存的 override/扫描值,缺失时回退到 pkg;getSeInfo() 优先读取 transient override,再读 transient base seInfo;getStateForUser() 从 SparseArray 取 user state,缺失时返回共享 DEFAULT。理解这种 fallback 比背字段表更重要。

3. 身份层 ​

3.1 SettingBase ​

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

java
public abstract class SettingBase implements Watchable, Snappable {
    private int mPkgFlags;
    private int mPkgPrivateFlags;

    private final Watchable mWatchable = new WatchableImpl();

    @Deprecated
    protected final LegacyPermissionState mLegacyPermissionsState =
            new LegacyPermissionState();

SettingBase 只提供 PackageSetting 与 SharedUserSetting 的公共底座:public/private flags、legacy permission migration state 和 Watchable 转发。mLegacyPermissionsState 注释明确说明它不能代表当前权限,只为旧持久化迁移服务;运行时权限 owner 是 PermissionManager。

java
public void onChanged() {
    PackageStateMutator.onPackageStateChanged();
    dispatchChange(this);
}

public SettingBase setFlags(int pkgFlags) {
    this.mPkgFlags = pkgFlags;
    onChanged();
    return this;
}

public SettingBase setPrivateFlags(int pkgPrivateFlags) {
    this.mPkgPrivateFlags = pkgPrivateFlags;
    onChanged();
    return this;
}

任何正常 setter 都应触发全局 state-change sequence 与 watcher。前者供 optimistic mutation 判断读取后是否被并发修改,后者使 PackageSetting/Settings/Computer snapshot 失效。直接改字段会绕过两个消费者,因此字段大多 private。

3.2 AppId关系 ​

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

java
@NonNull
private String mName;

@Nullable
private String mRealName;

private int mAppId;
private int mPccId;

/**
 * The shared user ID lets us link this object to {@link SharedUserSetting}.
 */
private int mSharedUserAppId;

mName 是 Settings map 的主键;mRealName 服务 package rename/original-package 迁移;mAppId 是不含 userId 高位的 appId,实际 UID 用 UserHandle.getUid(userId, appId) 组合;mSharedUserAppId 只是指向 SharedUserSetting 的关联键,不携带 shared user 的包集合与签名状态。

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

java
boolean registerAppIdLPw(PackageSetting p, boolean forceNew)
        throws PackageManagerException {
    final boolean createdNew;
    if (p.getAppId() == 0 || forceNew) {
        p.setAppId(mAppIds.acquireAndRegisterNewAppId(p));
        createdNew = true;
    } else {
        createdNew = mAppIds.registerExistingAppId(
                p.getAppId(), p, p.getPackageName());
    }
    if (p.getAppId() < 0) {
        PackageManagerService.reportSettingsProblem(Log.WARN,
                "Package " + p.getPackageName() + " could not be assigned a valid UID");
        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                "Package " + p.getPackageName() + " could not be assigned a valid UID");
    }
    return createdNew;
}

PackageSetting 自己保存 appId,但唯一性 owner 是 Settings 的 AppIdSettingMap。新包可分配 appId,重启恢复则注册已存在 appId;冲突或空间耗尽会让安装/扫描失败。SharedUser 包通常让 mAppId 与 SharedUserSetting appId 相同,同时把 mSharedUserAppId 设为该关联值。

hasSharedUser() 的实现只检查 mSharedUserAppId > 0。不要使用 raw 值推导 SharedUserSetting 内容,实际对象必须通过 Settings/appId map 查询;下一篇 shared UID 专题会继续展开成员与签名约束。

3.3 布尔位图 ​

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

java
// Use a bitset to store boolean data to save memory
private static class Booleans {
    private static final int INSTALL_PERMISSION_FIXED = 1;
    private static final int UPDATE_AVAILABLE = 1 << 1;
    private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
    private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
    private static final int PENDING_RESTORE = 1 << 4;
    private static final int DEBUGGABLE = 1 << 5;
    private static final int IS_LEAVING_SHARED_USER = 1 << 6;
    private static final int VERIFY_COMPILATION_ARTIFACTS = 1 << 7;
}
private int mBooleans;

顶层位图只保存非用户维度布尔状态。installed、stopped、hidden 等不能放这里,因为不同用户值不同;它们有独立 per-user bitset。部分值来自 Manifest 但被缓存到 PackageSetting,例如 debuggable;部分由运行期策略写入,例如 update available、pending restore、leaving shared user。

flags 与 boolean bitset 也不是一回事。ApplicationInfo.FLAG_SYSTEM、private partition flags 等仍在 SettingBase 的 int 中;PackageSetting 的 Booleans 用于没有合适 ApplicationInfo bit 或需要独立持久化语义的状态。

4. 解析对象 ​

4.1 pkg可空 ​

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

java
/**
 * This can be null whenever a physical APK on device is missing. This can be the result of
 * removing an external storage device where the APK resides.
 *
 * This is important in order to enforce uniqueness within the system, as the package, even if
 * on a removed storage device, is still considered installed. Another package of the same
 * application ID or declaring the same permissions or similar cannot be installed.
 */
@Nullable
AndroidPackage getAndroidPackage();

pkg == null 是设计状态,不是空指针 bug。典型来源包括:外部卷拔出、OTA 后 APK 在新 SDK 无法解析、DELETE_KEEP_DATA 保留数据、应用归档。PackageSetting 继续保留 appId、签名、路径、用户状态等,防止另一个包抢占身份,并允许介质恢复或重新安装后重新绑定 AndroidPackage。

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

java
removePackageLI(packageName, (flags & PackageManager.DELETE_CHATTY) != 0);
if (!deletedPs.isSystem()) {
    // A non-system app's AndroidPackage object has been removed from the service.
    // Explicitly nullify the corresponding app's PackageSetting's pkg object to
    // prevent any future usage of it, in case the PackageSetting object will remain because
    // of DELETE_KEEP_DATA.
    deletedPs.setPkg(null);
}

if (!shouldDeletePackageSetting && !deletedPs.isSystem()
        && outInfo.mRemovedUsers != null) {
    for (int userId : outInfo.mRemovedUsers) {
        deletedPs.setInstalled(false, userId);
    }
    if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
        deletedPs.setSplitNames(deletedPkg.getSplitNames());
        deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
    }
}

DELETE_KEEP_DATA 删除 active parsed package,却保留 PackageSetting,并把受影响用户 installed 设 false。split 名称与 revision 被复制到 PackageSetting,供未来 downgrade check;否则 pkg 清空后这些 Manifest 派生值会丢失。

4.2 回退getter ​

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

java
@NonNull
public String[] getSplitNames() {
    if (pkg != null) {
        return pkg.getSplitNames();
    }
    return mSplitNames == null ? EmptyArray.STRING : mSplitNames;
}

@Nullable
public String getPrimaryCpuAbi() {
    if (TextUtils.isEmpty(mPrimaryCpuAbi) && pkg != null) {
        return AndroidPackageUtils.getRawPrimaryCpuAbi(pkg);
    }
    return mPrimaryCpuAbi;
}

@Nullable
public String getPrimaryCpuAbiLegacy() {
    return mPrimaryCpuAbi;
}

effective getter 与 raw persisted getter有不同契约。getPrimaryCpuAbi() 可以回退 AndroidPackage;getPrimaryCpuAbiLegacy() 只返回 PackageSetting 内部值,用于 Settings 兼容与迁移。调用者若只是生成 ApplicationInfo,应使用 effective getter;若需要判断 XML 中是否显式保存 ABI,才使用 legacy getter。

split getter 的方向相反:pkg 存在时信任本次解析结果,pkg 为空时才使用 DELETE_KEEP_DATA/archived 情况保存的 fallback arrays。Settings 写 packages.xml 时也只在 pkg == null 时序列化 split fallback,避免复制正常 AndroidPackage 已拥有的数据。

4.3 归档状态 ​

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

java
pkgSetting
        // No package.
        .setPkg(null)
        // Mark for later restore.
        .setPendingRestore(true);
for (int userId : userIds) {
    // Unmark "installed" for all users.
    pkgSetting
            .modifyUserState(userId)
            .setInstalled(false);
}

归档同时跨越三层:顶层 pkg=null 表示 APK 内容不可用;顶层 pendingRestore 表示未来安装/更新需恢复;每用户 installed=false,且 ArchiveState 保存在对应 PackageUserStateImpl。归档 title/icon/time 不是 PackageSetting 顶层字段,因为同一包可在某用户归档、另一个用户仍有不同状态。

5. 临时派生层 ​

5.1 字段边界 ​

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

java
/**
 * For use by {@link PackageSetting} to maintain functionality that used to exist in PackageParser.
 *
 * It is assumed that anything inside the package was not cached or written to disk, so none of
 * these fields are either. They must be set on every boot from other state on the device.
 */
public class PackageStateUnserialized {
    private boolean hiddenUntilInstalled;

    @NonNull
    private List<SharedLibraryWrapper> usesLibraryInfos = emptyList();
    @NonNull
    private List<String> usesLibraryFiles = emptyList();

    private boolean updatedSystemApp;
    private boolean apkInUpdatedApex;

    @NonNull
    private volatile long[] lastPackageUsageTimeInMills;

    @Nullable
    private String overrideSeInfo;
    @NonNull
    private String seInfo;
    @Nullable
    private String mApexModuleName;

这些字段不是 packages.xml 的核心 record:resolved library graph、updated-system 派生标志、seInfo 和 APEX module 在每次扫描中重建。PackageSetting#getTransientState() 暴露该对象给内部消费者,但外部只读接口通常通过 getSeInfo()、getSharedLibraryDependencies() 等 effective API 读取。

有两个源码边界值得注意:

  1. 类注释称 cloned PackageSetting 不复制 transient state,但 Android 17 的 copyPackageSetting() 实际调用 pkgState.updateFrom(other.pkgState);应以当前控制流为准,这段注释已经与实现发生偏差。
  2. usage timestamps 不进 packages.xml,却由独立 PackageUsage 写入 package-usage.list;“unserialized”准确表达的是不属于 Settings XML,而不是永远不落任何磁盘文件。

5.2 使用时间 ​

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

java
public PackageStateUnserialized setLastPackageUsageTimeInMills(int reason, long time) {
    if (reason < 0) {
        return this;
    }
    if (reason >= PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT) {
        return this;
    }
    getLastPackageUsageTimeInMills()[reason] = time;
    // This method does not notify snapshot changes because it's called too
    // frequently, causing too many re-takes.
    return this;
}

高频 usage 更新故意不调用 onChanged(),否则 ContentProvider、Activity、Service 等使用事件会持续重建完整 package snapshot。消费者接受该数组不具备 snapshot point-in-time 保证;PackageUsage 再用独立低频文件保存它。

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

java
PackageUsage() {
    super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true);
}

for (PackageSetting pkgSetting : pkgSettings.values()) {
    if (pkgSetting == null || pkgSetting.getPkgState() == null
            || pkgSetting.getPkgState().getLatestPackageUseTimeInMills() == 0L) {
        continue;
    }
    sb.append(pkgSetting.getPackageName());
    for (long usageTimeInMillis : pkgSetting.getPkgState()
            .getLastPackageUsageTimeInMills()) {
        sb.append(' ');
        sb.append(usageTimeInMillis);
    }
}

这是一种有意的状态拆分:安全与查询一致性敏感字段触发 snapshot invalidation;高频启发式 usage 不拖累主 snapshot,并使用自己的持久化节奏。

5.3 seInfo覆盖 ​

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

java
@Nullable
@Override
public String getSeInfo() {
    String overrideSeInfo = getTransientState().getOverrideSeInfo();
    if (!TextUtils.isEmpty(overrideSeInfo)) {
        return overrideSeInfo;
    }

    return getTransientState().getSeInfo();
}

基础 seInfo 与 compatibility change 产生的 override 都在 transient state。getSeInfo() 的消费者不应直接读取基础字段,否则 SELinux compat listener 更新 override 后仍得到旧 domain。override 修改通过 PackageStateMutator 触发 PackageSetting change,并准备 app data 重新应用标签。

6. 用户矩阵 ​

6.1 默认状态 ​

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

java
@NonNull
private final SparseArray<PackageUserStateImpl> mUserStates = new SparseArray<>();

@NonNull
public PackageUserStateInternal readUserState(int userId) {
    PackageUserStateInternal state = mUserStates.get(userId);
    if (state == null) {
        return PackageUserStateInternal.DEFAULT;
    }
    return state;
}

PackageUserStateImpl modifyUserState(int userId) {
    PackageUserStateImpl state = mUserStates.get(userId);
    if (state == null) {
        state = new PackageUserStateImpl(this);
        mUserStates.put(userId, state);
        onChanged();
    }
    return state;
}

读取不存在的 user state 不分配对象,而是返回共享 immutable DEFAULT;第一次写才创建 PackageUserStateImpl。这减少系统包在大量用户下的默认状态内存占用。

DEFAULT 的语义不是“包不存在”:installed=true、stopped=false、hidden=false、enabled=default、firstInstallTime=0。PackageUserState 接口注释明确警告,查询不存在的 userId 也会得到 installed default,调用方需要先验证用户是否真实存在。Computer 的公开查询通常先调用 UserManager.exists(),直接使用 PackageSetting 的内部代码不能漏掉该检查。

6.2 用户字段 ​

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

java
private static class Booleans {
    private static final int INSTALLED = 1;
    private static final int STOPPED = 1 << 1;
    private static final int NOT_LAUNCHED = 1 << 2;
    private static final int HIDDEN = 1 << 3;
    private static final int INSTANT_APP = 1 << 4;
    private static final int VIRTUAL_PRELOADED = 1 << 5;
    private static final int APP_LOCK_ENABLED = 1 << 6;
}
private int mBooleans;

private long mCeDataInode;
private long mDeDataInode;
private long mPccCeDataInode;
private long mPccDeDataInode;
private int mDistractionFlags;
private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
private int mUninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;

此外还有 enabled/disabled components、overlay paths、suspending package→SuspendParams map、harmful warning、splash theme、first install time、min aspect ratio、archive state、component label/icon override 等。它们共同构成 (packageName, userId) 状态,而不是 package 全局属性。

dataExists() 不等于 installed:

java
@Override
public boolean dataExists() {
    return getCeDataInode() > 0 || getDeDataInode() > 0
            || getPccCeDataInode() > 0 || getPccDeDataInode() > 0;
}

DELETE_KEEP_DATA 后 installed=false,但 inode 仍可使 dataExists=true;查询带 MATCH_UNINSTALLED_PACKAGES 或 MATCH_ARCHIVED_PACKAGES 时可返回此记录。普通卸载若数据也清理,两个条件都为 false。

6.3 新包初态 ​

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

java
// If this is not a system app, it starts out stopped.
if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
    List<UserInfo> users = getAllUsers(userManager);
    if (users != null && allowInstall) {
        for (UserInfo user : users) {
            final boolean installed = installUser == null
                    || (installUserId == UserHandle.USER_ALL
                        && !isAdbInstallDisallowed(userManager, user.id)
                        && !user.preCreated)
                    || installUserId == user.id;
            pkgSetting.setUserState(user.id,
                    0 /* ceDataInode */,
                    0 /* deDataInode */,
                    0 /* pccCeDataInode */,
                    0 /* pccDeDataInode */,
                    COMPONENT_ENABLED_STATE_DEFAULT,
                    installed,
                    true /* stopped */,
                    true /* notLaunched */,
                    false /* hidden */,
                    0 /* distractionFlags */,
                    null /* suspendParams */,
                    instantApp,
                    virtualPreload,
                    null /* lastDisableAppCaller */,
                    null /* enabledComponents */,
                    null /* disabledComponents */,
                    PackageManager.INSTALL_REASON_UNKNOWN,
                    PackageManager.UNINSTALL_REASON_UNKNOWN,
                    null /* harmfulAppWarning */,
                    null /* splashscreenTheme */,
                    0 /* firstInstallTime */,
                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
                    null /* archiveState */,
                    false /* appLockEnabled */,
                    PackageManager.VIRTUAL_GAMEPAD_USER_OPTION_UNSET,
                    PackageManager.PERSONAL_CONTEXT_MODE_UNSET);
        }
    }
}

新非系统包默认 stopped=true、notLaunched=true;installed 只对目标用户、允许的 USER_ALL 用户或未指定目标时成立。系统包通常不创建显式 default state,除非 stopped-system-app 或用户类型 policy 需要覆盖。

stopped 与 enabled 是正交维度:enabled 决定包/组件是否允许解析启用;stopped 影响 force-stop、广播与首次启动语义。不要把 COMPONENT_ENABLED_STATE_DISABLED_USER 与 stopped 混成一个“禁用”字段。

6.4 生效算法 ​

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

java
public static boolean isAvailable(@NonNull PackageUserState state, long flags) {
    final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
    final boolean matchUninstalled =
            (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
    final boolean matchArchived =
            (flags & PackageManager.MATCH_ARCHIVED_PACKAGES) != 0;
    final boolean matchDataExists = matchUninstalled || matchArchived;

    if (matchAnyUser) {
        return true;
    }
    if (state.isInstalled()) {
        if (!state.isHidden()) {
            return true;
        } else return matchDataExists;
    } else {
        return matchDataExists && state.dataExists();
    }
}

PackageSetting 字段本身不直接决定 API 输出,Computer/PackageInfoUtils 会调用这些 policy helper。相同 PackageUserState 在不同 flags 下可能可见或不可见。MATCH_ANY_USER 更特殊:PackageSetting 存在意味着至少某用户安装或有数据,所以直接返回 true,但调用方仍需具备对应权限。

enabled 算法先看 package enabled state,再看 component 显式 enabled/disabled,最后回退 Manifest component enabled;MATCH_DISABLED_COMPONENTS 可绕过普通 disable,quarantine 则需要 MATCH_QUARANTINED_COMPONENTS。状态消费者的 flags 是结果的一部分。

7. 变更传播 ​

7.1 Watch链 ​

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

java
private void onChanged() {
    if (mWatchable != null) {
        mWatchable.dispatchChange(mWatchable);
    }
    dispatchChange(this);
}

public PackageUserStateImpl setInstalled(boolean value) {
    setBoolean(Booleans.INSTALLED, value);
    onChanged();
    return this;
}

PackageUserStateImpl 保存 parent Watchable,通常是 PackageSetting。用户字段改变时先通知 parent,再通知自己的 snapshot cache。PackageSetting 的 onChanged() 又增加 PackageStateMutator sequence,并向 Settings/PMS watcher 分发,最终使 Computer snapshot pending version 增加。

嵌套 WatchedArraySet/WatchedArrayMap 也注册 snapshot observer。例如 enabled components 集合被直接 add/remove 时,集合变化仍会使 user-state snapshot 失效;返回普通 untracked collection 的 API 则禁止或隔离外部修改。

7.2 Mutation提交 ​

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

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

public PackageStateMutator.Result commitPackageStateMutation(
        @Nullable PackageStateMutator.InitialState initialState,
        @NonNull String packageName,
        @NonNull Consumer<PackageStateWrite> consumer) {
    PackageStateMutator.Result result = null;
    if (Thread.holdsLock(mPackageStateWriteLock)) {
        // If the thread is already holding the lock, this is likely a retry based on a prior
        // failure, and re-calculating whether a state change occurred can be skipped.
        result = PackageStateMutator.Result.SUCCESS;
    }
    synchronized (mPackageStateWriteLock) {
        if (result == null) {
            result = mPackageStateMutator.generateResult(
                    initialState, mChangedPackagesTracker.getSequenceNumber());
        }
        if (result != PackageStateMutator.Result.SUCCESS) {
            return result;
        }

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

        state.onChanged();
    }
    return PackageStateMutator.Result.SUCCESS;
}

mPackageStateWriteLock 在生产 PMS 中就是 mLock。调用方可先记录 package-set sequence 与 state-change sequence,基于 snapshot 做权限/业务判断,再提交;中间若包集合或任意 PackageSetting 状态变化,generateResult() 返回冲突,调用方可在锁内重新验证。

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

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;
    } else {
        return Result.SUCCESS;
    }
}

这不是数据库 transaction rollback。consumer 在获得 SUCCESS 后直接改 live PackageSetting;冲突检查只保护“基于旧 snapshot 做出的决策”不会静默提交。consumer 抛异常时没有自动字段回滚,因此 consumer 应保持小而确定。

7.3 持久化分离 ​

mutation 只改变内存并触发 snapshot invalidation,不自动判断写哪个文件。调用方必须根据字段 owner 决定是否以及何时调度持久化:

  • MIME group、category、installer、page-size flags 等顶层状态调用 scheduleWriteSettings();
  • hidden、harmful warning、enabled/stopped 等主要 user-state 路径会调用 scheduleWritePackageRestrictions(userId);部分 setter 路径如当前 setSplashScreenTheme() 只提交内存状态,没有在该方法中立即调度,需依赖后续 Settings flush;
  • 高频 transient usage 不写 Settings,而由 PackageUsage 处理;
  • 某些纯运行期 transient 字段在下次扫描重建,不落盘。

例如 hidden mutation 后立即基于新 snapshot kill/发广播,再调度 user 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);
}

scheduleWritePackageRestrictions(userId);

生效时机分三层:live state 在 commit 内同步改变;查询 snapshot 在下一次 snapshotComputer() 时重建;磁盘在延迟 restrictions write 时提交。崩溃窗口内这三者可能处于不同版本,持久化专题已解释恢复边界。

8. Copy与快照 ​

8.1 普通copy ​

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

java
PackageSetting(PackageSetting orig) {
    this(orig, false);
}

public PackageSetting(@NonNull PackageSetting original,
        boolean sealedSnapshot) {
    super(original);
    copyPackageSetting(original, sealedSnapshot);
    if (sealedSnapshot) {
        mSnapshot = new SnapshotCache.Sealed();
    } else {
        mSnapshot = makeCache();
    }
}

普通 copy 不是完整 deep copy。标量、arrays、mime map、keyset 等有不同复制策略;pkg、InstallSource 和 signatures 等对象可共享;最重要的是 user-state 在 sealedSnapshot=false 时直接复用原对象:

java
for (int i = 0; i < other.mUserStates.size(); i++) {
    if (sealedSnapshot) {
        mUserStates.put(other.mUserStates.keyAt(i),
                other.mUserStates.valueAt(i).snapshot());
    } else {
        var userState = other.mUserStates.valueAt(i);
        userState.setWatchable(this);
        mUserStates.put(other.mUserStates.keyAt(i), userState);
    }
}

源码注释也明确称 shallow copy 的 user-state 修改会同时影响 original 与 copy。普通 copy 常用于 system package disable/替换等受控流程,不能交给无锁读者当历史快照。

8.2 Sealed快照 ​

PackageSetting.snapshot() 通过 SnapshotCache 创建 new PackageSetting(source, true)。sealed 分支对每个 user state 调用 snapshot();PackageUserStateImpl snapshot constructor 对 watched collections 调 snapshot,并把自己的 cache 替换为 SnapshotCache.Sealed。

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

java
public PackageUserStateImpl(@NonNull Watchable watchable,
        PackageUserStateImpl other) {
    mWatchable = watchable;
    mBooleans = other.mBooleans;
    mDisabledComponentsWatched = other.mDisabledComponentsWatched == null
            ? null : other.mDisabledComponentsWatched.snapshot();
    mEnabledComponentsWatched = other.mEnabledComponentsWatched == null
            ? null : other.mEnabledComponentsWatched.snapshot();
    mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths == null
            ? null : other.mSharedLibraryOverlayPaths.snapshot();
    mSuspendParams = other.mSuspendParams == null
            ? null : other.mSuspendParams.snapshot();
    mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
    mArchiveState = other.mArchiveState;
    mSnapshot = new SnapshotCache.Sealed<>();
}

sealed 表示 watched collection 与 snapshot cache 不能继续生成可变分支,并通过 PackageState/PackageUserState 只读接口限制普通消费者;它不是给每个 concrete scalar setter 自动插入 freeze 检查。若 package-private 代码把 snapshot 强转回实现类并直接调用 setInstalled() 一类 setter,简单标量仍可能被修改。只读性依赖接口能力边界与受保护的嵌套集合共同成立,不是 Java 对象级深冻结。AndroidPackageInternal 按只读/immutable 契约共享,InstallSource 是 immutable value,其他部分 immutable value 也会直接复用。

8.3 Transient复制 ​

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

java
pkgState.updateFrom(other.pkgState);
onChanged();

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

java
public void updateFrom(PackageStateUnserialized other) {
    this.hiddenUntilInstalled = other.hiddenUntilInstalled;
    if (!other.usesLibraryInfos.isEmpty()) {
        this.usesLibraryInfos = new ArrayList<>(other.usesLibraryInfos);
    }
    if (!other.usesLibraryFiles.isEmpty()) {
        this.usesLibraryFiles = new ArrayList<>(other.usesLibraryFiles);
    }
    this.updatedSystemApp = other.updatedSystemApp;
    this.apkInUpdatedApex = other.apkInUpdatedApex;
    this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
    this.overrideSeInfo = other.overrideSeInfo;
    this.seInfo = other.seInfo;
    this.mApexModuleName = other.mApexModuleName;
    mPackageSetting.onChanged();
}

Android 17 实际会复制 transient state,usage array 甚至共享同一引用。这个行为再次说明 sealed PackageSetting snapshot 的目的主要是给只读 API 提供不可修改结构,而不是构造完全独立、可长期保存的对象图。

9. 更新合并 ​

9.1 updateFrom ​

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

java
/** Updates all fields in the current setting from another. */
public void updateFrom(PackageSetting other) {
    copyPackageSetting(other, false /* sealedSnapshot */);

    Set<String> mimeGroupNames =
            other.mimeGroups != null ? other.mimeGroups.keySet() : null;
    updateMimeGroups(mimeGroupNames);

    onChanged();
}

updateFrom() 不是“只更新 APK 内容并自动保留当前 user states”。它调用 copyPackageSetting(),包括 other 的 appId、path、flags、user-state 引用、installSource 与 transient state。复制循环会覆盖 other 中存在的 userId,但不会先清空目标 mUserStates,所以目标独有的额外 userId 仍可能保留;它既不是“完全不更新用户状态”,也不是严格的 map replacement。

MIME group 额外处理的目标是只保留新 Manifest 仍声明的 group name,并保留同名 group 的运行时 MIME type 集合;新声明 group 创建空集合,已删除 group 移除。

9.2 扫描更新 ​

生产扫描更新更多使用 Settings.updatePackageSetting(),它按业务规则有选择地修改既有对象。

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

java
if (sharedUser != null) {
    if (!Objects.equals(existingSharedUserSetting, sharedUser)) {
        PackageManagerService.reportSettingsProblem(Log.WARN,
                "Package " + pkgName + " shared user changed from "
                        + (existingSharedUserSetting != null
                        ? existingSharedUserSetting.name : "<nothing>")
                        + " to " + sharedUser.name);
        throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED,
                "Updating application package " + pkgName + " failed");
    }
    pkgSetting.setSharedUserAppId(sharedUser.mAppId);
} else {
    // migrating off shared user
    pkgSetting.setSharedUserAppId(INVALID_UID);
}

更新不能任意切换到另一个 SharedUser;否则 appId、数据目录 ownership、权限与进程隔离全部失效,直接返回 INSTALL_FAILED_UID_CHANGED。迁出 shared UID 是单独支持的状态,并有 isLeavingSharedUser 等迁移标志。

路径变化时,若普通 data 包变成 system 包,代码把所有允许用户 installed 设 true;DONT_KILL 更新会把旧 path 加入 mOldPaths,延迟删除旧代码;ABI、domainSetId、target SDK、restrict hash 与 library arrays 按新扫描结果更新。

java
if (!oldCodePath.equals(codePath)) {
    final boolean isSystem = pkgSetting.isSystem();
    if (!isSystem) {
        if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 && disabledPkg == null) {
            final List<UserInfo> allUserInfos = getAllUsers(userManager);
            if (allUserInfos != null) {
                for (UserInfo userInfo : allUserInfos) {
                    pkgSetting.setInstalled(true, userInfo.id);
                    pkgSetting.setUninstallReason(
                            UNINSTALL_REASON_UNKNOWN, userInfo.id);
                }
            }
        }
        pkgSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPath);
    }
    pkgSetting.setPath(codePath);
    if (isDontKill) {
        pkgSetting.addOldPath(oldCodePath);
    }
}

9.3 Flag保留 ​

Settings.updatePackageSetting 并不把全部 pkgFlags 原样覆盖:它只从新扫描值更新 FLAG_SYSTEM,其他 SettingBase flags 保留;private flags 使用新值,但特别保留原有 PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER。这防止运行期/系统配置写入的 required-for-system-user 属性被一次重扫意外清掉。

java
int newPkgFlags = pkgSetting.getFlags();
newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
pkgSetting.setFlags(newPkgFlags);

boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags()
        & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
if (wasRequiredForSystemUser) {
    pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
} else {
    pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
}
pkgSetting.setPrivateFlags(pkgPrivateFlags);

更新的核心不是统一 copy 策略,而是按字段 owner 选择“来自新 APK”“保留旧设备策略”“按用户迁移”“禁止变化”。修改该函数时必须逐字段确认来源。

10. 持久化映射 ​

10.1 全局状态 ​

Settings持久化 已展开文件协议,这里只映射 PackageSetting 字段:

PackageSetting 数据packages.xml 表达
name、realName、path<package name realName codePath>
appId/sharedUserAppId/PCC IDuserId、sharedUserId、pccId
flags、version、timestamps、target SDKpackage attributes
ABI、volume、installSourcepackage attributes
signatures、keysetsnested sig/keyset tags
SDK/static libraries、MIME groupsnested tags
domainSetId、metadata、developer verificationpackage attributes/root domain data
split fallback只在 pkg == null 时写 nested split data

AndroidPackageInternal pkg 本身不序列化;启动扫描重新解析 APK。PackageStateUnserialized 大部分不进 packages.xml;last usage 由 package-usage.list 单独保存。top-level boolean 中 updateAvailable、forceQueryable、pendingRestore、debuggable 等有对应 attribute;临时 leaving-shared-user 或 compilation verification 是否持久化,需要看 Settings writer 是否读取,不能仅凭字段存在推断。

10.2 用户状态 ​

PackageUserStateImpl 写入每用户 package-restrictions.xml。installed=false、stopped=true 等非默认布尔值以 attribute 表达;enabled/disabled component、suspend params、archive state 等写 nested tag;firstInstallTime 已从旧全局 package timestamp 迁到 per-user state。

同一 PackageSetting 因而可能在 user 0 installed=true、user 10 installed=false 但 dataExists=true、user 11 hidden=true。生成 ApplicationInfo/PackageInfo 时必须同时传 PackageSetting 和目标 user state,单看顶层无法回答“该用户是否能看到/启动应用”。

10.3 删除清理 ​

完整卸载删除 Settings map 与 appId 注册;单用户卸载只改 PackageUserState;DELETE_KEEP_DATA 还保留 PackageSetting 与 data inode;用户删除则移除该 userId 的 SparseArray entry、restrictions 和 runtime permission owner。结束路径取决于删除范围,不是所有 uninstall 都销毁 PackageSetting。

11. 边界条件 ​

11.1 默认用户陷阱 ​

getUserStateOrDefault(nonexistentUser) 返回 installed=true 的 DEFAULT。任何直接使用 PackageStateInternal 的系统服务若没有先调用 UserManager.exists(),都可能把不存在用户误判为已安装。公开 Computer 查询通常防御这一点,但新内部 API 需要显式审查。

11.2 Mutable暴露 ​

PackageSetting#getUserStates() 返回底层 SparseArray view,getUsesSdkLibraries() 返回内部 array,部分 internal getter 返回 no-copy watched collection。这些 API 假设调用者在 system_server 内遵守只读契约;真正对较广消费者暴露的 PackageState sealed snapshot 会用 unmodifiable collection 或 snapshot。不能把 internal 接口的返回对象交给不受控代码修改。

11.3 值域防御 ​

源码中存在不同防御策略:

  • setMimeGroup() 遇到未声明 group 抛 IllegalArgumentException;
  • SDK/static library arrays 长度不一致时 Settings.updatePackageSetting 清空整组依赖;
  • loading progress 只允许单调增加,较小值被忽略;
  • page-size compat mode 非法时记录 error 并不修改;
  • install/uninstall reason setter执行 annotation validation;
  • PackageStateMutator.Result 定义了 SPECIFIC_PACKAGE_NULL,但 Android 17 当前 forPackage() 总是返回 StateWriteWrapper,目标不存在时只是 wrapper 内部 state 为 null,setter 静默 no-op;PMS 随后的 state == null 检查因此不会由这条路径命中。阅读调用方时不能假设该 result 一定可靠地产生。

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

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

@NonNull
private StateWriteWrapper setState(@Nullable PackageSetting state) {
    if (state != null) {
        mChangedStates.add(state);
    }
    return mStateWrite.setState(state);
}

public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
    if (mState != null) {
        mState.setUpdateAvailable(updateAvailable);
    }
    return this;
}

这里返回值始终是 wrapper;null 只保存在 mState。这是当前源码行为与 PMS state == null 防御分支之间的不一致,调用 API 若必须对 unknown package 抛错,应在提交前用 snapshot 明确验证目标存在。

失败语义必须按字段风险设计。UID/shared user 变化是安装 fatal;UI category 或 unknown package 可能只是 API error;高频 usage reason 越界直接忽略。

11.4 通知例外 ​

多数 setter 调 onChanged(),但并非机械一致。例如 usage time 故意不通知 snapshot;setInstallPermissionsFixed() 只改 PackageSetting bit,没有显式 onChanged;某些批量方法内部 user-state setter已通知,外层又通知一次。阅读性能或 stale snapshot 问题时,要追实际 setter,而不是假设所有字段都由 DataClass 自动处理。

12. 测试证据 ​

12.1 Copy测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java

java
@Test
public void testPackageStateCopy02() {
    final PackageSetting origPkgSetting01 = new PackageSetting(
            PACKAGE_NAME, REAL_PACKAGE_NAME, INITIAL_CODE_PATH,
            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_HAS_CODE,
            ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
                    | ApplicationInfo.PRIVATE_FLAG_HIDDEN,
            UUID.randomUUID())
            .setPrimaryCpuAbi("x86_64")
            .setSecondaryCpuAbi("x86")
            .setLongVersionCode(INITIAL_VERSION_CODE);
    origPkgSetting01.setUserState(0, 100, 100, 101,
            102, 1, true, false, false, false, 0, null, false,
            false, "lastDisabledCaller",
            new ArraySet<>(new String[]{"enabledComponent1"}),
            new ArraySet<>(new String[]{"disabledComponent1"}),
            0, 0, "harmfulAppWarning", "splashScreenTheme", 1000L,
            PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null,
            false, PackageManager.VIRTUAL_GAMEPAD_USER_OPTION_UNSET,
            PackageManager.PERSONAL_CONTEXT_MODE_UNSET);

    final PackageSetting testPkgSetting01 = new PackageSetting(
            PACKAGE_NAME, REAL_PACKAGE_NAME, UPDATED_CODE_PATH,
            0, 0, UUID.randomUUID());
    testPkgSetting01.copyPackageSetting(origPkgSetting01, true);

    verifySettingCopy(origPkgSetting01, testPkgSetting01);
    verifyUserStatesCopy(origPkgSetting01.readUserState(0),
            testPkgSetting01.readUserState(0));
}

输入包含 flags、ABI、version、四类 inode、enabled/disabled components、warning、theme 与 first install time;动作是 copyPackageSetting(..., true);断言逐字段比较顶层与 user state。它验证 sealed copy 的数据保真,不证明普通 copy 与原对象隔离,普通 copy 按源码明确共享 user-state 对象。

同文件的 testUpdatePackageSetting01() 先把 user 0 installed=false,再调用 Settings.updatePackageSetting() 修改 path/ABI/target SDK;断言 user 0 仍 false。testUpdatePackageSetting02() 把新扫描 flags 改为 system,断言同一用户转为 installed=true,并验证 system/privileged flags。这两项反向验证更新规则取决于 data→system 转换,而非统一“总是保留用户状态”。

12.2 UserState测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java

java
@Test
public void testPackageUserState05() {
    final ArrayMap<UserPackage, SuspendParams> paramsMap1 = new ArrayMap<>();
    paramsMap1.put(UserPackage.of(0, "package1"),
            createSuspendParams(dialogInfo1, appExtras1, launcherExtras1));
    final ArrayMap<UserPackage, SuspendParams> paramsMap2 = new ArrayMap<>();
    paramsMap2.put(UserPackage.of(10, "package2"),
            createSuspendParams(dialogInfo2, appExtras2, launcherExtras2));

    final PackageUserStateImpl testUserState1 = new PackageUserStateImpl();
    testUserState1.setSuspendParams(paramsMap1);

    PackageUserStateImpl testUserState2 =
            new PackageUserStateImpl(null, testUserState1);
    assertThat(testUserState1.equals(testUserState2), is(true));
    try {
        testUserState2.setSuspendParams(paramsMap2);
        Assert.fail("Changing sealed snapshot of suspendParams should throw");
    } catch (IllegalStateException e) {
        assertThat(e.getMessage().contains(
                "attempt to change a sealed object"), is(true));
    }
}

输入是包含 suspending user/package、dialog 与 extras 的 suspend map;snapshot constructor 复制后 equals 必须为 true;修改 sealed map 必须抛异常。它验证嵌套 WatchedArrayMap 的 sealed 行为,不代表 PackageUserStateImpl 所有引用都递归深拷贝。

同测试类逐个改变 ce inode、enabled、hidden、installed、notLaunched、stopped、suspend params、uninstall reason 和 app-lock flag,断言 equals 会检测变化;还验证 archive time 默认取创建时刻、显式 timestamp 被保留,以及 overlay path 写入相同值不会报告 change。

12.3 只读集合 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt

kotlin
@Test
fun collectionImmutability() {
    val seenTypes = mutableSetOf<KType>()
    val (_, pkg) = AndroidPackageTest().buildBefore()
    val packageState = PackageSettingBuilder()
        .setPackage(pkg as AndroidPackage)
        .setCodePath(tempFolder.newFile().path)
        .build()

    fillMissingData(packageState, pkg as PackageImpl)

    visitType(seenTypes, emptyList(), PackageSetting(packageState, true),
        PackageState::class.starProjectedType)
    visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0),
        PackageUserState::class.starProjectedType)

    expect.that(seenTypes.size).isGreaterThan(10)
}

测试构造真实 AndroidPackage、PackageSetting sealed snapshot、library/MIME/component/overlay collection,并用 Kotlin reflection 遍历 PackageState/PackageUserState 无参 getter。对 collection 调 clear() 必须抛 UnsupportedOperationException,对 map 同样测试;至少遍历十种类型,避免反射失效后空跑。它证明 API collection immutability,不证明 internal no-copy getter安全。

12.4 Transient测试 ​

PackageUserStateTest.testPackageUseReasons() 对 reason=-1、等于 count、Integer.MAX_VALUE 写 usage,断言数组保持全 0;对 0、CONTENT_PROVIDER 和 count-1 写入,断言只有目标 slot 更新。它验证越界静默忽略与按 reason 分桶,但没有验证 PackageUsage 文件 round-trip或 snapshot不失效。

13. 源码推演 ​

给定“包在 Settings 中存在,但 getAndroidPackage() 返回 null”,先判断是 external storage、DELETE_KEEP_DATA、archive 还是扫描拒绝;再查看目标 user 的 installed/dataExists/archive state 与查询 flags。不要立刻删除 PackageSetting,它可能仍在保护 appId、签名、权限声明或用户数据。

给定“只对 user 10 隐藏应用”,应修改 PackageUserStateWrite.userState(10).setHidden(true),提交后用新 Computer snapshot执行 kill/广播,并调度 user 10 restrictions;不能写顶层 PackageSetting boolean,也不能重写所有用户。

给定“新增 PackageState 字段”,至少要回答五个问题:它来自 Manifest 还是设备策略;是否按用户;重启后是否必须保留;查询是否要求 point-in-time snapshot;修改时应写 global Settings、user restrictions、独立文件还是完全不持久化。字段放错层会造成多用户串扰、OTA 丢失、snapshot stale 或高频重建。

最后,看到 new PackageSetting(old) 时不要默认得到深拷贝。只有 sealed snapshot 对 user-state/watched collections建立只读副本;普通 copy 可能共享 user-state,pkg 与 immutable value 也可能共享。判断隔离性必须沿 copyPackageSetting() 逐字段检查,而不是依赖“copy constructor”这个名字。