Skip to content

PMS构造函数

将 Android 17 PMS 构造函数拆成状态 owner 安装、双锁重建事务、扫描合并、Settings 提交和临界区离场五个阶段。

基于android-17.0.0_r1
AndroidPackageManagerServicePMSSettingsPackage扫描源码阅读

PMS构造函数 ​

本文进入 PackageManagerService 的生产构造函数,面向已经读过 PMS架构全景 和 PMS启动时序 的读者。前两篇分别建立 PMS 的接口/状态地图和 SystemServer 外部时序;本文不再重复 Binder 发布与 systemReady(),而是回答构造函数内部更具体的问题:一个空的 Java 对象如何把 Settings 中的旧状态、系统分区与 /data/app 的磁盘事实合并为本次启动可查询的包数据库。

正确的心智模型不是“构造函数依次 new 了很多 Helper”,而是一次启动期状态重建事务。事务开始前,PMS 先安装状态 owner、Local 接口、Helper 和 live Computer;事务期间同时持有 mInstallLock 与 mLock,恢复 Settings、扫描包、修正升级状态和派生索引;事务提交时写回 Settings、创建依赖扫描结果的服务、重建 live 查询视图;离开临界区后才解除 cache cork、启用 installd 锁告警,并设置启动期广播延时窗口。

本文只追踪构造函数如何编排这些对象和状态。Settings XML 字段、SharedUser 数据结构、Computer snapshot 算法、每个扫描目录与单包扫描算法都有独立专题,本文在相应位置给出真实入口和边界,不用一段伪“完整代码”替代它们。

1. 构造契约 ​

1.1 参数不变量 ​

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

java
public PackageManagerService(PackageManagerServiceInjector injector, boolean factoryTest,
        final String partitionsFingerprint, final boolean isEngBuild,
        final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion,
        final int sdkVersionFull) {
    mIsEngBuild = isEngBuild;
    mIsUserDebugBuild = isUserDebugBuild;
    mSdkVersion = sdkVersion;

    mSdkVersionFull = sdkVersionFull;
    // If the major version of sdkVersionFull and sdkVersion are not equal,
    // throw RuntimeException to crash the system.
    if (Build.getMajorSdkVersion(sdkVersionFull) != sdkVersion) {
        throw new RuntimeException("sdkVersionFull:" + sdkVersionFull + " and sdkVersion: "
                + sdkVersion + " don't match. Please check your build configurations!");
    }

    mIncrementalVersion = incrementalVersion;
    mInjector = injector;

构造入口有八个参数。injector 提供对象依赖;partitionsFingerprint 与 Settings 中保存的 fingerprint 比较,决定 OTA/升级路径;eng、userdebug 与 incremental version 参与 watcher 校验和 parser cache;两个 SDK 值进入 Settings version 与升级兼容逻辑。

第一条硬不变量发生在任何扫描之前:sdkVersionFull 的 major 必须等于 sdkVersion。失败时对象不会进入 bootstrap、不会读 Settings,也不会扫描磁盘。它不是可恢复的单包错误,而是构建配置自相矛盾,源码明确要求抛 RuntimeException 终止系统启动。

factoryTest 在该构造函数中只赋给 mFactoryTest。Android 17 的 PackageManagerService.java 没有其他读取 mFactoryTest 的生产代码,因此不能沿用旧版本经验,声称 factoryTest=true 会跳过包扫描或权限初始化。

1.2 缓存封锁 ​

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

java
mInjector.getSystemWrapper().disablePackageCaches();

final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
        Trace.TRACE_TAG_PACKAGE_MANAGER);
mPendingBroadcasts = new PendingPackageBroadcasts();

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

java
private static class DefaultSystemWrapper implements
        PackageManagerServiceInjector.SystemWrapper {

    @Override
    public void disablePackageCaches() {
        // disable all package caches that shouldn't apply within system server
        PackageManager.disableApplicationInfoCache();
        PackageManager.disablePackageInfoCache();
        invalidateGetPackagesForUidCache(
                PackageMetrics.INVALIDATION_REASON_DISABLE_PACKAGE_CACHES);
        ApplicationPackageManager.disableGetPackagesForUidCache();
        ApplicationPackageManager.invalidateHasSystemFeatureCache();
        PackageManager.corkPackageInfoCache();
        ApplicationPackageManager.invalidateQueryIntentActivitiesCache();
        ApplicationPackageManager.disableQueryIntentActivitiesCacheForCurrentProcess();
        AppOpsManager.disableCheckPackageCache();
    }

    @Override
    public void enablePackageCaches() {
        PackageManager.uncorkPackageInfoCache();
    }
}

构造开始先处理查询缓存,因为接下来会连续改变包集合、feature、UID 映射和 Intent 索引。如果让 system_server 当前进程继续命中旧的 ApplicationInfo、PackageInfo 或 queryIntentActivities 缓存,扫描中间态可能逃逸到后续读取。

这里有一个容易被方法名掩盖的非对称设计:入口会禁用多个不适合 system_server 的缓存,并 cork package-info invalidation;构造尾部的 enablePackageCaches() 只执行 uncorkPackageInfoCache()。被明确禁用的当前进程缓存不会全部重新开启,它们在 system_server 中本来就不应提供陈旧副本。

构造函数的高层数据流如下。图中的“提交”不是数据库事务 API,而是对源码锁区间、状态合并和持久化边界的抽象。

2. Owner落位 ​

2.1 Injector回填 ​

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

java
mInjector.bootstrap(this);
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
mInstallLock = injector.getInstallLock();
LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
        SystemClock.uptimeMillis());

mContext = injector.getContext();
mFactoryTest = factoryTest;
mMetrics = injector.getDisplayMetrics();
mInstaller = injector.getInstaller();
mFreeStorageHelper = new FreeStorageHelper(this);

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

java
/**
 * Bootstraps this injector with the {@link PackageManagerService instance to which it
 * belongs.
 */
public void bootstrap(PackageManagerService pm) {
    this.mPackageManager = pm;
}

Injector 在 main() 中已经保存所有 producer,但 producer 需要当前 PMS 才能创建 Settings、UserManager、ComponentResolver 等双向依赖对象。bootstrap(this) 只是把 PMS 引用写入 Injector,不会自动实例化全部子组件;后面的 getter 才触发对应 Singleton producer。

mPackageStateWriteLock 与 mLock 指向同一个对象,表达的是包状态写入的语义别名,不是第三把锁。LockGuard.installLock() 登记包锁的全局锁级别,供锁序诊断使用。BOOT_PROGRESS_PMS_START 在锁与 Injector 可用后写出,后续启动日志才能把构造内部耗时归到 PMS。

2.2 本地接口 ​

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

java
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
LocalManagerRegistry.addManager(PackageManagerLocal.class,
        new PackageManagerLocalImpl(this));
LocalServices.addService(TestUtilityService.class, this);
mTestUtilityService = LocalServices.getService(TestUtilityService.class);
mUserManager = injector.getUserManagerService();
mUserNeedsBadging = new UserNeedsBadgingCache(mUserManager);
mComponentResolver = injector.getComponentResolver();
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mIncrementalManager = mInjector.getIncrementalManager();
mDefaultAppProvider = mInjector.getDefaultAppProvider();
mLegacyPermissionManager = mInjector.getLegacyPermissionManagerInternal();

Local 接口早于 Settings 读取和包扫描注册,但这不等于远程 Binder 已经发布。调用范围被限制在 system_server 进程,主要目的是解决构造期 producer 的真实依赖。例如 AppsFilterImpl.create() 会从 Injector 获取 PackageManagerInternal;若 LocalServices 注册放到扫描后,AppsFilter 无法按当前依赖图创建。

这段顺序同时安装四个核心 owner:

owner构造期写入后续消费者
UserManagerServiceuserId 列表、用户类型与安装状态Settings、扫描、allowlist、权限与 app-data
ComponentResolverActivity/Service/Provider/Receiver 索引required package 解析、Intent 查询
PermissionManagerServiceInternallegacy 权限迁移、volume mount 更新权限检查、默认权限与包扫描
SettingsPackageSetting、SharedUser、version 与 per-user 状态Computer、安装/删除、持久化写回

顺序重要,但不能简化为“前一个完整构造后一个”。Injector 的 singleton producer 允许对象在创建时反向获取 PMS 或其他 owner;真正需要判断的是某个 getter 之前,它的 producer 所依赖的 Local 接口和字段是否已经可用。

2.3 固定UID ​

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

java
t.traceBegin("addSharedUsers");
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.se", SE_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

这些固定 SharedUser 必须在读取 packages.xml 前进入 Settings 的 appId 注册表。文件中的包若声明对应 shared UID,反序列化时需要把 package setting 关联到已经存在的 SharedUserSetting。因此它们是恢复旧状态的 schema seed,不是扫描完成后的派生结果。

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

java
SharedUserSetting addOemSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    if (!name.startsWith("android.uid")) {
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                "Failed to add oem defined shared user because of invalid name: " + name);
        return null;
    }
    // OEM defined uids must be in the OEM reserved range
    if (uid < 2900 || uid > 2999) {
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                "Failed to add oem defined shared user because of invalid uid: " + uid);
        return null;
    }
    return addSharedUserLPw(name, uid, pkgFlags, pkgPrivateFlags);
}

SystemConfig 还可以提供 OEM UID,但名字必须以 android.uid 开头,数值必须落在 2900~2999。非法配置会记录 Settings problem 并返回 null,不会把任意 UID 注入 appId 映射。具体 SharedUser 的包成员、签名和迁移由后续数据结构专题展开。

2.4 解析环境 ​

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

java
String separateProcesses = SystemProperties.get("debug.separate_processes");

if (separateProcesses != null && separateProcesses.length() > 0) {
    if ("*".equals(separateProcesses)) {
        mDefParseFlags = ParsingPackageUtils.PARSE_IGNORE_PROCESSES;
        mSeparateProcesses = null;
        Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
    } else {
        mDefParseFlags = 0;
        mSeparateProcesses = separateProcesses.split(",");
        Slog.w(TAG, "Running with debug.separate_processes: "
                + separateProcesses);
    }
} else {
    mDefParseFlags = 0;
    mSeparateProcesses = null;
}

debug.separate_processes 在 parser producer 被第一次消费前读取。值为 * 时设置 PARSE_IGNORE_PROCESSES;值为逗号列表时保存到 mSeparateProcesses;未设置时走普通解析。这里仅建立 PackageParser2 的输入条件,不应把属性名直接解释成“PMS 会把每个应用 fork 到独立进程”。

构造函数还创建 PackageParser2.Callback:compat 查询委托 PlatformCompat,feature 查询回到 PMS,hidden API allowlist 与 install constraints allowlist 来自 SystemConfig。解析器因此不是只读 XML,它会消费当前 build 的兼容性和设备 feature 状态。

3. Helper依赖 ​

3.1 创建顺序 ​

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

java
mDomainVerificationConnection = new DomainVerificationConnection(this);
mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
mDomainVerificationManager.setConnection(mDomainVerificationConnection);

mBroadcastHelper = new BroadcastHelper(mInjector);
mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper();
mAppDataHelper = new AppDataHelper(this);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper, mBroadcastHelper);
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
        mBroadcastHelper);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper,
        mDeletePackageHelper, mBroadcastHelper);

mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
        mInjector.getUserManagerInternal(), mDeletePackageHelper);

mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
mPreferredActivityHelper = new PreferredActivityHelper(this, mBroadcastHelper);
mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
        injector.getCompatibility(), mUserManager, mDomainVerificationManager,
        mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity);
mDexOptHelper = new DexOptHelper(this);
mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
        mProtectedPackages);
mAppLockPackageHelper = new AppLockPackageHelper(mContext, this, mBroadcastHelper);
mDistractingPackageHelper = new DistractingPackageHelper(this, mBroadcastHelper,
        mSuspendPackageHelper);
mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
        mRemovePackageHelper);

Helper 的顺序是一张依赖图,不是任意清单。RemovePackageHelper 需要 app-data 与广播;DeletePackageHelper 复用 remove;InstallPackageHelper 在扫描和安装时都可能调用 app-data/remove/delete;SharedLibraries 在处理更新或冲突时要回调 delete;StorageEventHelper 在卷 reconcile 时复用 delete/remove。

ResolveIntentHelper 的两个 Supplier 解决构造时机问题:mResolveInfo 已作为字段存在,而 instant-app installer activity 要到扫描后才能确定。Supplier 推迟读取,不要求 Helper 构造时该组件已经解析出来。

3.2 消费链 ​

图中箭头表示构造参数或后续调用依赖,不表示每个 Helper 自己拥有一份包数据库。状态仍由 PMS、Settings、ComponentResolver、PermissionManager 等 owner 管理;Helper 负责在锁协议内协调修改和副作用。把 Helper 当作独立 service 会误判锁、持久化和测试替换边界。

4. 查询基线 ​

4.1 Live基线 ​

源码文件: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);
}

Computer computer = mLiveComputer;

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

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

live Computer 必须在 Settings 读取前存在,因为 Settings.readLPw()、共享库解析、required package 查询和扫描 helper 都接收 Computer。Snapshot.LIVE 直接观察当前 owner,不是构造结束后给 Binder 读者使用的冻结副本。

构造期变量 computer 一直引用最初的 mLiveComputer。扫描会修改它所观察的 live 状态,所以不需要每写一个包就重新赋值;构造尾部重建 mLiveComputer 是为了把后续重建过的属性和 owner 引用纳入新的 live 基线。

4.2 Observer连线 ​

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

java
private void registerObservers(boolean verify) {
    if (mPackages != null) {
        mPackages.registerObserver(mWatcher);
    }
    if (mSharedLibraries != null) {
        mSharedLibraries.registerObserver(mWatcher);
    }
    if (mAppsFilter != null) {
        mAppsFilter.registerObserver(mWatcher);
    }
    if (mInstantAppRegistry != null) {
        mInstantAppRegistry.registerObserver(mWatcher);
    }
    if (mSettings != null) {
        mSettings.registerObserver(mWatcher);
    }
    if (mComponentResolver != null) {
        mComponentResolver.registerObserver(mWatcher);
    }
    if (verify) {
        Watchable.verifyWatchedAttributes(
                this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
    }
}

Observer 把多个状态 owner 的变化汇聚到 mWatcher,最终通过 onChange() 增加 sSnapshotPendingVersion。正常查询比较 cached snapshot version 与 pending version,不一致时才重建。这里的顺序不变量是“先安装所有需要观察的 owner,再开始大规模扫描写入”;否则构造期间的状态变化可能不触发 snapshot invalidation。

测试专用构造函数调用 registerObservers(false),因为 mock 字段可能为空或不是完整的 @Watched 图;生产构造调用 true,还执行属性验证。这两个构造函数服务不同目的,不能拿极简测试构造函数解释真实开机扫描。

5. 双锁事务 ​

5.1 锁序 ​

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

java
try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
// writer
synchronized (mLock) {
    mHandler = injector.getHandler();
    mProcessLoggingHandler = new ProcessLoggingHandler();
    Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

    // Settings 恢复、系统/data 扫描、修正与写回都在该范围内。

源码注释用 writer 标记这是启动期写事务。外层先获取 mInstallLock,内层再进入 mLock,符合 PMS 允许的锁序。此时 Installer 文件操作、Settings map、mPackages、ComponentResolver 和 PermissionManager 需要共同收敛,不能让运行期读写在中间穿插。

PackageHandler 在进入事务后取得,并注册到 Watchdog。SystemServer 主线程的 Watchdog 监控在 PMS.main() 外层暂时暂停,但 PMS 自己的 handler 从此进入长期监控;两者 owner 与生命周期不同。

5.2 共享库 ​

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

java
ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
        = systemConfig.getSharedLibraries();
final int builtInLibCount = libConfig.size();
for (int i = 0; i < builtInLibCount; i++) {
    mSharedLibraries.addBuiltInSharedLibraryLPw(libConfig.valueAt(i));
}

// Now that we have added all the libraries, iterate again to add dependency
// information IFF their dependencies are added.
long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED;
for (int i = 0; i < builtInLibCount; i++) {
    String name = libConfig.keyAt(i);
    SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
    final int dependencyCount = entry.dependencies.length;
    for (int j = 0; j < dependencyCount; j++) {
        final SharedLibraryInfo dependency =
            computer.getSharedLibraryInfo(entry.dependencies[j], undefinedVersion);
        if (dependency != null) {
            computer.getSharedLibraryInfo(name, undefinedVersion)
                    .addDependency(dependency);
        }
    }
}

内置共享库分两遍处理:第一遍先把所有库注册,第二遍才建立 dependency。若单遍边注册边连边,配置中先出现的库可能引用尚未创建的后继库。第二遍只在 dependency 已存在时连接,缺失依赖不会在这里制造虚构节点。

这些是 SystemConfig 提供的 built-in library;APK 声明的 dynamic/static/SDK library 要在扫描后才能完整确定。构造稍后调用 updateAllSharedLibrariesLPw(),为最终保留的包更新共享库路径。两次动作处理的是不同来源和阶段。

5.3 Settings恢复 ​

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

java
if (!SELinuxMMAC.readInstallPolicy()) {
    throw new RuntimeException("Unable to load SELinux MMAC policy");
}

t.traceBegin("loadFallbacks");
FallbackCategoryProvider.loadFallbacks();
t.traceEnd();

t.traceBegin("read user settings");
mFirstBoot = !mSettings.readLPw(computer,
        mInjector.getUserManagerInternal().getUsers(/* excludeDying= */ false));
t.traceEnd();

if (mFirstBoot) {
    t.traceBegin("setFirstBoot: ");
    try {
        mInstaller.setFirstBoot();
    } catch (InstallerException e) {
        Slog.w(TAG, "Could not set First Boot: ", e);
    }
    t.traceEnd();
}

mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
mPermissionManager.readLegacyPermissionStateTEMP();

SELinux install policy 必须先于扫描,因为每个包的 seInfo 与 shared UID domain 计算都依赖它;加载失败是 fatal。Fallback category 与 Settings 随后恢复。readLPw() 返回 false 才表示没有可用 Settings,PMS 取反写入 mFirstBoot。

Settings 不是权限状态的唯一 owner。读取 packages.xml 后,PMS 显式把 legacy permission definitions/state 交给 PermissionManager 临时迁移接口,之后扫描与 volume mount 回调才能在新的权限 owner 上继续工作。Installer.setFirstBoot() 失败只记录 warning,不会把已经判断出的 first-boot 状态回滚。

6. 状态合并 ​

6.1 启动类型 ​

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

java
final VersionInfo ver = mSettings.getInternalVersion();

// Read isMockUpgrade for the first time.
mIsMockUpgrade = SystemProperties.getBoolean("persist.pm.mock-upgrade",
        false /* default */);

mIsUpgrade = !partitionsFingerprint.equals(ver.fingerprint);
if (mIsUpgrade) {
    PackageManagerServiceUtils.logCriticalInfo(Log.INFO,
            "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
                    + PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mPriorSdkVersionFull = mIsUpgrade ? ver.sdkVersionFull : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
        mInjector.getSystemPartitions());

mPromoteSystemApps =
        mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;

first boot 与 upgrade 是两个独立维度。first boot 来自 Settings 是否存在;真实 upgrade 来自分区 fingerprint 是否变化;persist.pm.mock-upgrade 让 isDeviceUpgrading() 返回 true,但不会把 mIsUpgrade 改成 true。因此只检查一个字段会漏掉测试/调试路径。

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

java
public boolean isDeviceUpgrading() {
    // allow instant applications
    return mIsUpgrade || mIsMockUpgrade;
}

InitAppsHelper 消费的是 isDeviceUpgrading() 和 isFirstBoot()。两者任一为 true,都会添加 SCAN_FIRST_BOOT_OR_UPGRADE;但只有真实 mIsUpgrade 才保存 prior SDK、清理 code cache、写 OTA duration 等。下面的状态图表达字段之间的真实区别。

6.2 两次扫描 ​

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

java
final WatchedArrayMap<String, PackageSetting> packageSettings =
    mSettings.getPackagesLocked();

if (isDeviceUpgrading()) {
    mExistingPackages = new ArraySet<>(packageSettings.size());
    for (int i = 0; i < packageSettings.size(); i++) {
        mExistingPackages.add(packageSettings.valueAt(i).getPackageName());
    }

    t.traceBegin("cross profile intent filter update");
    mInjector.getCrossProfileIntentFilterHelper()
            .updateDefaultCrossProfileIntentFilter();
    t.traceEnd();
}

mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
        mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);

final int[] userIds = mUserManager.getUserIds();
PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
        startTime);
mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime);
packageParser.close();

Settings map 是“上次提交的期望状态”,扫描结果是“本次磁盘事实”。系统扫描先处理 APEX、system/product/vendor 等来源,并建立 mExpectingBetter、可能被删除的 updated system app 和 stub 包集合;非系统扫描再处理 /data/app,决定 data 更新是否仍优于 system 版本,并清理不再成立的 disabled setting。

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

java
InitAppsHelper(PackageManagerService pm, ApexManager apexManager,
        InstallPackageHelper installPackageHelper,
        List<ScanPartition> systemPartitions) {
    mPm = pm;
    mApexManager = apexManager;
    mInstallPackageHelper = installPackageHelper;
    mSystemPartitions = systemPartitions;
    mDirsToScanAsSystem = getSystemScanPartitions();
    mIsDeviceUpgrading = mPm.isDeviceUpgrading();
    // Set flag to monitor and not change apk file paths when scanning install directories.
    int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
    if (mIsDeviceUpgrading || mPm.isFirstBoot()) {
        mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
    } else {
        mScanFlags = scanFlags;
    }
    mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
    mSystemScanFlags = mScanFlags | SCAN_AS_SYSTEM;
    mExecutorService = ParallelPackageParser.makeExecutorService();
}

构造函数只调用两个高层入口,不等于扫描是串行单线程。InitAppsHelper 自己创建 ParallelPackageParser executor;非系统扫描结束后会 shutdownNow() 并要求没有 unfinished task,保证并行任务不能越过构造提交点。目录级细节留给包扫描系列。

6.3 派生索引 ​

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

java
mRequiredVerifierPackages = getRequiredButNotReallyRequiredVerifiersLPr(computer);
mRequiredInstallerPackage = getRequiredInstallerLPr(computer);
mRequiredUninstallerPackage = getRequiredUninstallerLPr(computer);
mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(computer);

mStorageManagerPackage = getStorageManagerPackageName(computer);
mSetupWizardPackage = getSetupWizardPackageNameImpl(computer);
mComponentResolver.fixProtectedFilterPriorities(mSetupWizardPackage);

mSharedLibraries.updateAllSharedLibrariesLPw(
        null, null, Collections.unmodifiableMap(mPackages));

for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
    final List<String> changedAbiCodePath =
            ScanPackageUtils.applyAdjustedAbiToSharedUser(setting,
                    null /*scannedPackage*/,
                    mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                            setting.getPackageStates(), null /*scannedPackage*/));
    setting.fixSeInfoLocked();
    setting.updateProcesses();
}

只有扫描后,Intent resolver 才能判断 installer、uninstaller、permission controller 等角色由哪个系统包承担。required installer 与 permission controller 要求唯一且 privileged,缺失或多匹配会抛异常;普通 ensureSystemPackageName() 路径则可能记录 warning 并返回 null。required 与 optional 的失败语义不同。

共享库 client path、shared UID ABI、seInfo 和 process 集合也必须基于最终保留包计算。changedAbiCodePath 在这段循环中没有继续消费,但 applyAdjustedAbiToSharedUser() 已对 setting 应用调整;随后 fixSeInfoLocked() 确保同 sharedUserId 的应用落入一致 SELinux domain。

7. 提交阶段 ​

7.1 权限与OTA ​

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

java
if (mIsUpgrade) {
    Slog.i(TAG, "Partitions fingerprint changed from " + ver.fingerprint + " to "
            + PackagePartitions.FINGERPRINT
            + "; regranting permissions for internal storage");
}
mPermissionManager.onStorageVolumeMounted(
        StorageManager.UUID_PRIVATE_INTERNAL, mIsUpgrade);
ver.sdkVersion = mSdkVersion;
ver.sdkVersionFull = mSdkVersionFull;

if (mPromoteSystemApps || mFirstBoot) {
    final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(true);
    for (int i = 0; i < users.size(); i++) {
        mSettings.applyDefaultPreferredAppsLPw(users.get(i).id);
    }
}

PermissionManager 的 volume mount 回调消费最终扫描结果,并用 mIsUpgrade 决定 fingerprint change 语义。Settings version 随后更新为当前 SDK。首次启动或 pre-M 升级还要应用默认 preferred apps;普通启动不会每次重建默认选择。

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

java
if (mIsUpgrade) {
    Slog.i(TAG, "Build fingerprint changed; clearing code caches");
    for (int i = 0; i < packageSettings.size(); i++) {
        final PackageSetting ps = packageSettings.valueAt(i);
        if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.getVolumeUuid())) {
            // No apps are running this early, so no need to freeze
            mAppDataHelper.clearAppDataLIF(ps.getPkg(), USER_ALL,
                    FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                            | Installer.FLAG_CLEAR_CODE_CACHE_ONLY
                            | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
        }
    }
    ver.buildFingerprint = Build.FINGERPRINT;
    ver.fingerprint = PackagePartitions.FINGERPRINT;
}

// Defer the app data fixup until we are done with app data clearing above.
mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();

OTA 只清 code cache,并显式保留 ART profiles。源码注释说明此时尚无应用运行,所以不需要先 freeze 包。清理结束后才启动 app-data fixup Future,避免后台 fixup 与前面的清理对同一目录交叉。Future 的消费者在 SystemServer 放行第三方应用前等待,已在启动时序专题中说明。

7.2 Settings提交 ​

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

java
// clear only after permissions and other defaults have been updated
mPromoteSystemApps = false;

// All the changes are done during package scanning.
ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;

// can downgrade to reader
t.traceBegin("write settings");
writeSettingsLPrTEMP();
t.traceEnd();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
        SystemClock.uptimeMillis());

mPromoteSystemApps 是一次性升级状态,权限/default 更新后清零;database version 到提交前才升级,表示当前内存数据已经按新 schema 处理完成。BOOT_PROGRESS_PMS_READY 紧随 Settings 写入,但仍处于双锁临界区,后面还有扫描结果派生对象。

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

java
void writeSettingsLPrTEMP(boolean sync) {
    snapshotComputer(false);
    mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
    mSettings.writeLPr(mLiveComputer, sync);
}

// Default async version.
void writeSettingsLPrTEMP() {
    writeSettingsLPrTEMP(/*sync=*/false);
}

提交前调用 snapshotComputer(false),强制 cached snapshot 版本追上当前 pending version;它的返回值没有继续传递,后面的 Settings.writeLPr() 仍接收 mLiveComputer。随后 PermissionManager 把 legacy permission 状态写回 Settings owner,Settings 再写 packages.xml、kernel mapping、package list、所有用户 restrictions 和 runtime permissions。这里应区分“刷新全局 snapshot cache”和“持久化时传入哪个 Computer”,不能把两者合并成冻结快照写入。

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

java
try (ResilientAtomicFile atomicFile = getSettingsFile()) {
    FileOutputStream str = null;
    try {
        str = atomicFile.startWrite();
        final TypedXmlSerializer serializer = Xml.resolveSerializer(str);
        serializer.startDocument(null, true);
        serializer.startTag(null, "packages");

        // 省略:写 version、permissions、packages、disabled packages、shared users。

        serializer.endTag(null, "packages");
        serializer.endDocument();
        atomicFile.finishWrite(str);

        writeKernelMappingLPr();
        writePackageListLPr();
        writeAllUsersPackageRestrictionsLPr(sync);
        writeAllRuntimePermissionsLPr();
        return;
    } catch (java.io.IOException e) {
        Slog.wtf(PackageManagerService.TAG,
                "Unable to write package manager settings, "
                        + "current changes will be lost at reboot", e);
        if (str != null) {
            atomicFile.failWrite(str);
        }
    }
}

这是构造函数一个重要的降级边界:I/O 失败会调用 failWrite() 恢复原子文件状态,并记录“当前变化在重启后丢失”,但 writeLPr() 不重新抛异常。当前内存状态仍可让本次启动继续;风险是下次启动无法恢复这次合并结果。不能把它写成“Settings 写失败必然阻止 PMS 构造”。

7.3 后置对象 ​

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

java
ComponentName intentFilterVerifierComponent =
        getIntentFilterVerifierComponentNameLPr(computer);
ComponentName domainVerificationAgent =
        getDomainVerificationAgentComponentNameLPr(computer, UserHandle.USER_SYSTEM);

DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
        intentFilterVerifierComponent, domainVerificationAgent, mContext,
        mDomainVerificationManager, mDomainVerificationManager.getCollector(),
        mDomainVerificationConnection);
mDomainVerificationManager.setProxy(domainVerificationProxy);

mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr(computer);
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibrary(computer,
        PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
        SharedLibraryInfo.VERSION_UNDEFINED);

mInstallerService = mInjector.getPackageInstallerService(
        mDeveloperVerificationServiceProvider);

DomainVerification proxy、services extension、shared system library 和 PackageInstallerService 都依赖扫描结果,因此不能在 owner 安装阶段提前创建。PackageInstallerService 的 producer 还需要 developer verification provider;该 provider 也是通过扫描后的组件查询决定。

构造尾部还重建 instant-app registry、读取 DynamicCodeLogger 使用记录、在 OTA 时写 duration metric,并在 first boot/upgrade 时为预装包设置 app metadata path。feature flag protectSystemRequiredPackages() 决定是否构建 required system packages 保护集合;aslInApkAppMetadataSource() 决定 metadata source 字段是否写入。它们都是条件路径,不可描述成所有 Android 17 设备无条件执行的状态。

最后一条锁内动作是:

java
// Rebuild the live computer since some attributes have been rebuilt.
mLiveComputer = createLiveComputer();

此时 Settings、resolver、权限、共享库、instant-app 与 required package 字段已经完成本轮重建。新的 live Computer 成为离开构造事务后的查询基线。

8. 临界区外 ​

8.1 缓存解封 ​

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

java
} // synchronized (mLock)
} // synchronized (mInstallLock)

mModuleInfoProvider = mInjector.getModuleInfoProvider();

mInjector.getSystemWrapper().enablePackageCaches();

mModuleInfoProvider 在释放两把锁后创建,说明它不参与启动包数据库的原子合并。随后只 uncork package-info cache;构造期间积累的 invalidation 可以向外生效。若在锁内 uncork,其他线程可能在提交尚未完全离场时开始重建缓存。

8.2 运行期告警 ​

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

java
// The initial scanning above does many calls into installd while
// holding the mPackages lock, but we're mostly interested in yelling
// once we have a booted system.
mInstaller.setWarnIfHeld(mLock);

ParsingPackageUtils.readConfigUseRoundIcon(mContext.getResources());

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

java
/**
 * Yell loudly if someone tries making future calls while holding a lock on
 * the given object.
 */
public void setWarnIfHeld(Object warnIfHeld) {
    mWarnIfHeld = warnIfHeld;
}

构造扫描期间,源码容许持有 mLock 调 installd,因为系统还没有进入正常并发运行。离开事务后才设置 mWarnIfHeld,未来慢 Binder 调用若在包锁内发生会产生强警告。这是“启动期特例”和“运行期锁纪律”的明确分界,不能用构造函数的双锁范围为普通安装代码辩护。

round-icon 配置也在锁外读取。它更新解析工具的全局资源配置,供后续运行期解析使用,不再改变本次已经完成的启动包集合。

8.3 启动延时窗 ​

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

java
mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);

该字段的消费者位于组件状态变更广播调度:构造结束后 60 秒内使用 10 秒广播延迟,超过窗口后使用 1 秒延迟。目的不是“PMS 60 秒后才可用”,而是启动早期避免大量 receiver 反应造成 thrashing。

构造函数返回时,以下状态的生命周期仍未结束:mPrepareAppDataFuture 要由 SystemServer 后续等待;mExistingPackages 要到 systemReady() staged session 恢复后清空;mServiceStartWithDelay 继续影响运行期广播调度。这说明 Java constructor return 只是核心重建事务结束,不代表所有启动期临时状态都已释放。

9. 条件矩阵 ​

构造函数中的关键条件不能只归纳成“首次启动更慢”。

条件字段/来源直接效果不会自动发生
Settings 缺失mFirstBootfirst-boot scan flag、preopt copy、默认 preferred apps不等于 mIsUpgrade=true
fingerprint 改变mIsUpgradeprior SDK、OTA 修正、code cache 清理、权限 volume upgrade不由 mock property 单独设置
mock upgrademIsMockUpgradeisDeviceUpgrading() 为 true,扫描使用 upgrade flag不保存真实 prior SDK,不执行所有 mIsUpgrade 分支
pre-M 升级mPromoteSystemApps默认 preferred apps 与权限迁移相关修正提交后字段被清零
pre-Q 升级mIsPreQUpgrade隐藏旧非系统应用 details activity普通升级不一定执行
eng/userdebug构造参数watcher verification、parser cache 策略不跳过扫描
debug.separate_processessystem property改变 PackageParser2 输入不直接创建应用进程
required-package flagprotectSystemRequiredPackages()构建保护集合flag 关闭时不构建

判断某个启动行为时,应先定位它读取 mFirstBoot、mIsUpgrade 还是 isDeviceUpgrading()。三者相似但不等价;测试也分别覆盖了这些组合。

10. 失败语义 ​

10.1 Fatal边界 ​

构造函数会直接终止的典型条件包括:

  • full SDK major 与 SDK 不匹配;
  • SELinux MMAC install policy 无法加载;
  • parallel parser 关闭时仍有 unfinished task;
  • required installer、uninstaller、permission controller 数量或 privilege 不满足;
  • required services extension/shared library 缺失;
  • 某些不可满足的扫描一致性不变量抛出未捕获异常。

这些异常会离开构造函数,PMS.main() 不会执行 Binder 注册。判断是否 fatal 的依据是异常是否在当前调用链被捕获,而不是日志级别看起来是否严重。

10.2 降级恢复 ​

可继续启动的路径包括:

  • Installer.setFirstBoot() 失败:记录 warning,保留 PMS 已判断的 first-boot 状态;
  • OEM shared UID 名称或范围非法:记录 Settings problem,不插入该 UID;
  • BOOTCLASSPATH/SYSTEMSERVERCLASSPATH 环境变量缺失:记录 warning,继续后续流程;
  • 某些 shared-user ABI 修正失败:报告 Settings 错误,继续系统扫描收敛;
  • Settings 写 I/O 失败:failWrite() 保留旧原子文件,本次内存状态继续,但重启可能丢失变化;
  • app metadata 配置指向不存在文件或包:路径置空或记录 warning,不终止整个构造。

Settings 读取损坏还有独立恢复层:ResilientAtomicFile 会尝试 backup/main/reserve copy。若初次 openRead() 就没有文件,readLPw() 返回 first boot;若已有文件在解析中抛异常,外层会删除坏副本并递归重试,同时故意忽略递归返回值,保留“不是 first boot”的语义。

10.3 扫描收敛 ​

构造启动测试展示了扫描失败不是统一语义:一个 data 包解析失败时,该包不会进入 mPackages;一个未出现在旧 Settings 的意外 data 包可能被拒绝;updated system app 则要比较 system 与 data 版本,保留更优版本。单包失败通常由扫描 helper 记录并局部处理,只有破坏全局不变量的异常才越过 helper 终止构造。

11. 测试证据 ​

11.1 构造测试 ​

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt

kotlin
@Test
@RequiresFlagsEnabled(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
fun sdkVersionNotMatched_throwException() {
    try {
        val pm = PackageManagerService(
            rule.mocks().injector,
            false /*factoryTest*/,
            MockSystem.DEFAULT_VERSION_INFO.fingerprint,
            false /*isEngBuild*/,
            false /*isUserDebugBuild*/,
            Build.VERSION_CODES.CUR_DEVELOPMENT,
            Build.VERSION.INCREMENTAL,
            0
        )
    } catch (e: Exception) {
        Truth.assertThat(e.message).contains(
            "don't match. Please check your build configurations!")
    }
}

输入是启用 major/minor version flag、当前 development SDK 与 sdkVersionFull=0 的不匹配组合;动作是直接调用生产构造函数;断言检查异常信息。它支持“SDK major 不变量在构造最前面失败”的结论,但测试写法没有显式 fail() 保证未抛异常时失败,因此证明力度主要来自源码条件与异常消息匹配的组合,而不是一个完美的 assertThrows。

同文件的 simpleConstruction() 使用 MockSystem.stageNominalSystemState() 准备 Injector、Settings、parser 和必需系统包,构造后验证 injector.bootstrap(pm) 被调用、固定 SharedUser 被写入、所有预期 Setting add 已消费。它验证的是 owner 安装和 nominal constructor convergence,不覆盖真实 installd I/O 或设备目录。

11.2 扫描测试 ​

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt

kotlin
@Test
fun existingDataPackage_remains() {
    rule.system().stageScanExistingPackage("a.data.package", 1L,
        rule.system().dataAppDirectory)
    val pm = createPackageManagerService()
    rule.system().validateFinalState()
    assertThat(pm.mPackages, hasKey("a.data.package"))
}

@Test
fun unexpectedDataPackage_isRemoved() {
    rule.system().stageScanNewPackage(
        "a.data.package", 1L, rule.system().dataAppDirectory)
    val pm = createPackageManagerService()
    verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw(
        argThat { setting: PackageSetting -> setting.name == "a.data.package" },
        argThat { pkg: AndroidPackage -> pkg.packageName == "a.data.package" })
    assertThat(pm.mPackages, not(hasKey("a.data.package")))
}

第一个测试同时准备旧 Setting 与可扫描 APK,断言构造后包仍在 mPackages;第二个只准备磁盘新包而不放入旧 Settings,断言没有插入 Setting 且包不在最终集合。它们反向验证构造函数不是“把目录里看到的所有 APK 无条件加入数据库”,而是在启动 flags 与 known-package 规则下合并旧状态和磁盘事实。

同文件还覆盖两个重要异常/冲突输入:让 parser 对旧 data package 抛 INSTALL_FAILED_INVALID_APK,最终包不进入 mPackages;system 分区有 v2、/data/app 有同签名 v3 的 updated system app 时,最终保留 v3。这些断言说明单包失败、unexpected package 与 expecting-better 是三种不同收敛路径。

11.3 标志测试 ​

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt

kotlin
@Test
fun testSystemScanFlagNoOTA() {
    mockNoFirstBoot()
    mockFingerprintUnchanged()
    val pms = createPackageManagerService()
    assertThat(pms.isFirstBoot).isEqualTo(false)
    assertThat(pms.isDeviceUpgrading).isEqualTo(false)
    val initAppsHelper = InitAppsHelper(
        pms, rule.mocks().apexManager, null, listOf<ScanPartition>())
    assertThat(
        initAppsHelper.systemScanFlags and
            PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE
    ).isEqualTo(0)
}

测试输入是已有 data package、fingerprint 未变化且没有 mock OTA;断言 first boot 与 upgrading 都为 false,并检查 scan flag 不含 SCAN_FIRST_BOOT_OR_UPGRADE。同文件分别把 Settings 缺失、mock OTA、fingerprint change 作为输入,三种情况都断言该 flag 被置位。它证明的是 InitAppsHelper flag 决策,不证明各 flag 下所有扫描目录和冲突算法都正确。

Settings version 还有独立测试:旧文件没有 sdkVersionFull 时,从 sdkVersion 补出 full version;写入再读取时同时保持 Build.VERSION.SDK_INT_FULL 与 SDK_INT。这些测试与构造入口的 major 检查共同覆盖“旧持久化格式迁移”和“当前构建参数一致性”两端。

12. 源码推演 ​

可以用三个真实问题复盘构造函数,而不是背阶段名称。

第一,设备升级后 system 分区出现 v2,但 /data/app 已有同签名 v3。应从 mIsUpgrade、mExistingPackages、InitAppsHelper.initSystemApps() 的 expecting-better 状态追到 initNonSystemApps() 和 fixSystemPackages(),最终解释为什么保留 data v3,而不是简单认为 system 分区优先。

第二,日志已有 BOOT_PROGRESS_PMS_READY,但重启后本次包状态丢失。应检查 Settings.writeLPr() 是否记录 Unable to write package manager settings,以及 ResilientAtomicFile.failWrite() 是否保留旧文件。PMS_READY 表示构造流程走过写入调用,不保证底层 I/O 一定成功持久化。

第三,构造期调用 installd 时同时持有 mLock,运行后同样行为却产生警告。应指出告警直到双锁事务结束后才通过 mInstaller.setWarnIfHeld(mLock) 启用;构造扫描是受控特例,普通安装/删除必须缩短包锁持有并遵守 Helper 的锁后缀协议。

沿源码阅读时,先在 PackageManagerService 构造函数定位 owner 安装、双锁范围和提交点,再进入 InitAppsHelper、Settings.writeLPr() 或具体 Helper。这样可以区分“谁编排状态重建”和“谁实现单包算法”,避免再次把 600 多行构造函数写成只有函数名的阶段清单。