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
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
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
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
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
/**
* 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
// 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 | 构造期写入 | 后续消费者 |
|---|---|---|
UserManagerService | userId 列表、用户类型与安装状态 | Settings、扫描、allowlist、权限与 app-data |
ComponentResolver | Activity/Service/Provider/Receiver 索引 | required package 解析、Intent 查询 |
PermissionManagerServiceInternal | legacy 权限迁移、volume mount 更新 | 权限检查、默认权限与包扫描 |
Settings | PackageSetting、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
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
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
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
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
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
/**
* 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
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
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
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
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
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
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
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
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
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
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
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
// 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
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
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
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 设备无条件执行的状态。
最后一条锁内动作是:
// 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
} // 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
// 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
/**
* 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
mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);该字段的消费者位于组件状态变更广播调度:构造结束后 60 秒内使用 10 秒广播延迟,超过窗口后使用 1 秒延迟。目的不是“PMS 60 秒后才可用”,而是启动早期避免大量 receiver 反应造成 thrashing。
构造函数返回时,以下状态的生命周期仍未结束:mPrepareAppDataFuture 要由 SystemServer 后续等待;mExistingPackages 要到 systemReady() staged session 恢复后清空;mServiceStartWithDelay 继续影响运行期广播调度。这说明 Java constructor return 只是核心重建事务结束,不代表所有启动期临时状态都已释放。
9. 条件矩阵
构造函数中的关键条件不能只归纳成“首次启动更慢”。
| 条件 | 字段/来源 | 直接效果 | 不会自动发生 |
|---|---|---|---|
| Settings 缺失 | mFirstBoot | first-boot scan flag、preopt copy、默认 preferred apps | 不等于 mIsUpgrade=true |
| fingerprint 改变 | mIsUpgrade | prior SDK、OTA 修正、code cache 清理、权限 volume upgrade | 不由 mock property 单独设置 |
| mock upgrade | mIsMockUpgrade | isDeviceUpgrading() 为 true,扫描使用 upgrade flag | 不保存真实 prior SDK,不执行所有 mIsUpgrade 分支 |
| pre-M 升级 | mPromoteSystemApps | 默认 preferred apps 与权限迁移相关修正 | 提交后字段被清零 |
| pre-Q 升级 | mIsPreQUpgrade | 隐藏旧非系统应用 details activity | 普通升级不一定执行 |
| eng/userdebug | 构造参数 | watcher verification、parser cache 策略 | 不跳过扫描 |
debug.separate_processes | system property | 改变 PackageParser2 输入 | 不直接创建应用进程 |
| required-package flag | protectSystemRequiredPackages() | 构建保护集合 | 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
@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
@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
@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 多行构造函数写成只有函数名的阶段清单。
