启动场景判定
本文面向已经读过 启动时间分析、预加载优化、开机动画时序 和 SystemServer.run 的读者。前面的文章告诉你如何测量启动;本文解决测量前的分组问题:一次慢启动究竟是普通重启、data wipe 后首次启动、OTA/Mainline 更新,还是 system_server runtime restart。
这些场景不是一个布尔值。Zygote、SystemServer、BootReceiver 和 PackageManager 各自维护不同状态,服务于统计、故障恢复、package 数据库和 dexopt。把它们混成“首次/普通”两类,会得到错误结论,例如把普通断电开机当作 PMS first boot,或认为 runtime restart 会跳过 package scan。读完后,你应能从持久化文件、fingerprint 和系统属性推导每个状态,并解释它最终改变了哪些消费者。
1. 四个维度
| 维度 | owner | 判据 | 生命周期 | 主要消费者 |
|---|---|---|---|---|
| Zygote runtime marker | ZygoteInit | sys.boot_completed == 1 | 当前物理 boot | StatsLog 抑制 |
| SystemServer restart | SystemServer | start_count > 1 | 当前物理 boot | dump、metrics、启动分支 |
| PMS first boot | PackageManager | 首次 openRead() 无 current/backup/reserve stream | data 分区状态 | installd、scan、默认配置、dexopt |
| PMS upgrading | PackageManager | partition fingerprint 变化 | 首次写入新 fingerprint 前 | scan、权限、cache、dexopt |
同一设备可能同时满足多个维度。例如 OTA 应用后、system_server 在 package scan 中崩溃并重启:第二个 SystemServer 是 runtime restart,PMS 仍可能认为 upgrading;sys.boot_completed 仍为空,Zygote 的局部统计标记却不一定认为它是 restart。
常见场景矩阵如下:
| 场景 | runtime restart | PMS first boot | PMS upgrading | boot dexopt reason |
|---|---|---|---|---|
| 普通关机后开机 | 否 | 否 | 否 | 通常无 |
| data wipe / factory reset | 否 | 是 | 否 | first-boot |
| OTA 后第一次成功 PMS 初始化 | 否 | 否 | 是 | boot-after-ota |
| BCP Mainline APEX 变化 | 否 | 否 | 可能否 | boot-after-mainline-update |
| boot 完成后的 system_server restart | 是 | 否 | 否 | 通常无 |
2. Runtime标记
2.1 Zygote标记
ZygoteInit 在进入 Java 主流程时读取 sys.boot_completed,但只用它决定是否上报 Zygote init StatsLog。它不改变 preload 或 fork 主流程。
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
相关函数:ZygoteInit.main
// 本文注:这是 Zygote 统计开关,不是 PMS first boot 判据。
final long startTime =
SystemClock.elapsedRealtime();
final boolean isRuntimeRestarted =
"1".equals(SystemProperties.get(
"sys.boot_completed"));
// ... 解析 Zygote 启动参数。
final boolean isPrimaryZygote =
zygoteSocketName.equals(
Zygote.PRIMARY_SOCKET_NAME);
if (!isRuntimeRestarted) {
if (isPrimaryZygote) {
FrameworkStatsLog.write(
FrameworkStatsLog
.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START,
startTime);
} else if (zygoteSocketName.equals(
Zygote.SECONDARY_SOCKET_NAME)) {
FrameworkStatsLog.write(
FrameworkStatsLog
.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START,
startTime);
}
}如果 system_server 在首次 boot completed 之前崩溃,属性仍不是 1,新 Zygote 的这个局部标记可能为 false;它不能作为进程恢复的权威状态。
2.2 SystemServer计数
SystemServer 通过 sys.system_server.start_count 记录当前物理 boot 内的启动次数。sys.* 属性在完整重启后重置,但 Zygote/system_server 软重启期间仍由 property service 保留。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关函数:SystemServer.SystemServer
public SystemServer() {
mFactoryTestMode = FactoryTest.getMode();
mStartCount = SystemProperties.getInt(
SYSPROP_START_COUNT, 0) + 1;
mRuntimeStartElapsedTime =
SystemClock.elapsedRealtime();
mRuntimeStartUptime =
SystemClock.uptimeMillis();
Process.setStartTimes(
mRuntimeStartElapsedTime,
mRuntimeStartUptime,
mRuntimeStartElapsedTime,
mRuntimeStartUptime);
// 本文注:count 的 owner 是 property service,不是当前 Java 进程。
mRuntimeRestart = mStartCount > 1;
}run() 随后把 count 与两种时钟写回属性,并记录 SYSTEM_SERVER_START EventLog。
相关函数:SystemServer.run
// ... run() 已完成早期时钟和 trace 初始化。
// 本文注:先写 property,后写 EventLog,两者都使用同一组构造期快照。
SystemProperties.set(SYSPROP_START_COUNT, String.valueOf(mStartCount));
SystemProperties.set(SYSPROP_START_ELAPSED, String.valueOf(mRuntimeStartElapsedTime));
SystemProperties.set(SYSPROP_START_UPTIME, String.valueOf(mRuntimeStartUptime));
EventLog.writeEvent(EventLogTags.SYSTEM_SERVER_START,
mStartCount, mRuntimeStartUptime, mRuntimeStartElapsedTime);
// ... 继续执行 locale、Looper 和 service 启动。这些 property 是后续 SystemServer 实例的输入,而 EventLog 是观测输出;它们共用同一个 mStartCount,但只有 property 会被下一个进程读取。dumpable 也直接输出 runtime restart 与 start count,因此无需从 PID 变化猜测。
相关函数:SystemServer.dump
@Override
public void dump(
PrintWriter pw, String[] args) {
// 本文注:这两项是诊断时的直接输出,无需用 PID 间接推断。
pw.printf("Runtime restart: %b\n",
mRuntimeRestart);
pw.printf("Start count: %d\n",
mStartCount);
pw.print("Runtime start-up time: ");
TimeUtils.formatDuration(
mRuntimeStartUptime, pw);
pw.println();
pw.print("Runtime start-elapsed time: ");
TimeUtils.formatDuration(
mRuntimeStartElapsedTime, pw);
pw.println();
}mRuntimeRestart 在构造期就固定,dump 只读取它;后续 BOOT_COMPLETED 不会反向修改这个布尔值。
2.3 Boot时间锚点
ro.runtime.firstboot 的名字容易误导。BootReceiver 只在收到 ACTION_BOOT_COMPLETED 后的后台线程中读写它;值为当前 wall-clock 毫秒。首次处理时写 SYSTEM_BOOT,同一 property-service 生命周期内再次处理则写 SYSTEM_RESTART。
源码文件:frameworks/base/services/core/java/com/android/server/BootReceiver.java
相关函数:BootReceiver.onReceive
@Override
public void onReceive(
final Context context, Intent intent) {
if (!Intent.ACTION_BOOT_COMPLETED.equals(
intent.getAction())) {
return;
}
// 本文注:写时间戳发生在 BOOT_COMPLETED 之后的后台线程。
new Thread() {
@Override
public void run() {
try {
logBootEvents(context);
} catch (Exception e) {
Slog.e(TAG,
"Can't log boot events", e);
}
// ... 清理旧 update package。
}
}.start();
// ... 注册 kernel trace pipe 监听。
}onReceive() 只是异步入口;真正的 property 判断在 logBootEvents() 内。因此该值的生效时机晚于 PMS 扫描、权限迁移和 boot dexopt。
相关函数:BootReceiver.logBootEvents
// ... logBootEvents() 先组装 headers 并获取 DropBoxManager。
// 本文注:ro.runtime.firstboot 保存的是 wall-clock 毫秒,不是布尔值。
if (SystemProperties.getLong(
"ro.runtime.firstboot", 0) == 0) {
String now = Long.toString(
System.currentTimeMillis());
SystemProperties.set(
"ro.runtime.firstboot", now);
if (db != null) {
db.addText("SYSTEM_BOOT", headers);
}
} else {
if (db != null) {
db.addText("SYSTEM_RESTART", headers);
}
}
// ... 继续记录 recovery、kernel 和 fsck 等启动事件。它在启动分支已经结束后才产生,PMS、SystemServer 和 DexOptHelper 都不消费它。它适合标记本次物理 boot 中第一次成功到达 BOOT_COMPLETED 的 system_server,不适合判断 data wipe 或 OTA。
3. PMS首次启动
PMS first boot 的内存 owner 是 PackageManagerService.mFirstBoot,持久化输入是 Settings 管理的 /data/system/packages.xml、临时 backup 和 reserve copy。它不是简单的“XML 解析成功”:只有 ResilientAtomicFile.openRead() 首次就找不到任何 stream 时,readLPw() 才返回 false。
源码文件:frameworks/base/services/core/java/com/android/server/pm/ResilientAtomicFile.java
相关函数:ResilientAtomicFile.openRead
public FileInputStream openRead() throws IOException {
// 本文注:临时 backup 优先,它存在时主文件被视为可能损坏。
if (mTemporaryBackup.exists()) {
try {
mCurrentFile = mTemporaryBackup;
mCurrentInStream = new FileInputStream(mCurrentFile);
// ... 删除被忽略的主文件与 reserve copy。
} catch (java.io.IOException e) {
// We'll try for the normal settings file.
}
}
if (mCurrentInStream != null) {
return mCurrentInStream;
}
if (mFile.exists()) {
mCurrentFile = mFile;
mCurrentInStream = new FileInputStream(mCurrentFile);
} else if (mReserveCopy.exists()) {
mCurrentFile = mReserveCopy;
mCurrentInStream = new FileInputStream(mCurrentFile);
}
return mCurrentInStream;
}读取顺序是 temporary backup → main file → reserve copy。所以“主 packages.xml 不存在”未必是 first boot;reserve copy 仍可以恢复已有 package 状态。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java
相关函数:Settings.readSettingsLPw
boolean readSettingsLPw(
@NonNull Computer computer,
@NonNull List<UserInfo> users,
ArrayMap<String, Long>
originalFirstInstallTimes) {
mPendingPackages.clear();
mInstallerPackages.clear();
originalFirstInstallTimes.clear();
try (ResilientAtomicFile atomicFile =
getSettingsFile()) {
FileInputStream str = null;
try {
str = atomicFile.openRead();
if (str == null) {
// 本文注:空 data 先对齐当前版本,避免再进入 OTA 分支。
findOrCreateVersion(
StorageManager
.UUID_PRIVATE_INTERNAL)
.forceCurrent();
findOrCreateVersion(
StorageManager
.UUID_PRIMARY_PHYSICAL)
.forceCurrent();
return false;
}
// ... 解析 packages、permissions、VersionInfo 等节点。
} catch (IOException | XmlPullParserException
| ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
atomicFile.failRead(str, e);
// 本文注:损坏文件会递归尝试下一份,但故意忽略 false。
readSettingsLPw(computer, users, originalFirstInstallTimes);
}
}
return true;
}这里有一个容易漏掉的恢复边界:首次 openRead() 返回 null 会得到 false;但已找到文件后发生 XML/I/O 损坏,外层调用故意忽略递归返回值,最终仍返回 true,避免把损坏恢复路径当成 data wipe。
readLPw() 对调用方的约定是“读取入口上没有 settings 文件”才返回 false;PMS 取反写入 mFirstBoot。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关函数:PackageManagerService.PackageManagerService
// ... PackageManagerService 构造期已创建 Settings 和 Computer。
t.traceBegin("read user settings");
// 本文注:Settings 的返回值在这里取反,mFirstBoot 之后不再重算。
mFirstBoot = !mSettings.readLPw(
computer,
mInjector.getUserManagerInternal()
.getUsers(
false /* excludeDying */));
t.traceEnd();
if (mFirstBoot) {
t.traceBegin("setFirstBoot: ");
try {
mInstaller.setFirstBoot();
} catch (InstallerException e) {
Slog.w(TAG,
"Could not set First Boot: ", e);
}
t.traceEnd();
}
// ... 继续进入 version 判定和 package scan。这就是“没有 package settings”的语义,典型来源是 data wipe/factory reset,手工删除 Settings 文件也会触发。普通断电重启中,正常的 main/backup/reserve 副本能被读到,因此不是 PMS first boot。
installd 暂时未连接时,Installer 保存 mDeferSetFirstBoot,重连后执行;远程异常则由 PMS 记录 warning 并继续启动。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Installer.java
相关函数:Installer.setFirstBoot
public void setFirstBoot()
throws InstallerException {
if (!checkBeforeRemote()) {
return;
}
try {
if (mInstalld != null) {
mInstalld.setFirstBoot();
} else {
// 本文注:Binder 重连后 executeDeferredActions() 会再次消费该状态。
mDeferSetFirstBoot = true;
}
} catch (Exception e) {
throw InstallerException.from(e);
}
}首次启动还可能请求复制 secondary system partition 的预优化文件。只有 ro.cp_system_other_odex=1 时执行,最多等待 100 秒;超时把 sys.cppreopt 写成 timed-out 并记录 wtf,不把设备永久卡死。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期首次启动分支
// ... PackageManagerService 构造期的 first-boot 分支。
if (mFirstBoot) {
// 本文注:真正的等待、超时和 property 协议在 DexOptHelper 内。
DexOptHelper.requestCopyPreoptedFiles();
}
// ... 后续创建 InitAppsHelper 并扫描 package。该调用是同步屏障,但只在 OEM 开启 ro.cp_system_other_odex 时真正等待;关闭时方法立即返回。
4. PMS升级判定
packages.xml 的 VersionInfo 持久化上一次 SDK、database version、Build fingerprint 与 PackagePartitions fingerprint。真正的 OTA 判据使用后者,因为它表示 system/vendor/product 等 package partitions 的组合状态。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java
相关类:Settings.VersionInfo
public static class VersionInfo {
int sdkVersionFull;
int sdkVersion;
int databaseVersion;
String buildFingerprint;
String fingerprint;
public void forceCurrent() {
// 本文注:空 data 将两种 fingerprint 都设为当前值。
sdkVersion = Build.VERSION.SDK_INT;
sdkVersionFull =
Build.VERSION.SDK_INT_FULL;
// ... 校验 full SDK 的 major version 与 SDK_INT 一致。
databaseVersion =
CURRENT_DATABASE_VERSION;
buildFingerprint = Build.FINGERPRINT;
fingerprint =
PackagePartitions.FINGERPRINT;
}
}PMS 在读取旧 settings 后比较 fingerprint,并额外支持 persist.pm.mock-upgrade 测试开关。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期版本判定
// ... 从 Settings 取得当前内部存储的 VersionInfo。
final VersionInfo ver =
mSettings.getInternalVersion();
mIsMockUpgrade =
SystemProperties.getBoolean(
"persist.pm.mock-upgrade",
false /* default */);
// 本文注:持久化 partition fingerprint 才决定 mIsUpgrade。
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;
// ... 根据 prior SDK 设置兼容迁移标志。isDeviceUpgrading() 返回 mIsUpgrade || mIsMockUpgrade。database schema 迁移可以因 databaseVersion 落后而发生,但不会单独把 mIsUpgrade 置为 true;两类迁移不要混为一谈。
升级时 PMS 先保存 scan 前的 package 名称集合,供 user-type allowlist 区分 OTA 新增系统包;还根据 prior SDK 设置 pre-M、pre-N MR1、pre-Q 兼容迁移。
5. 扫描主线
first boot、OTA 和普通启动都会调用 initSystemApps() 与 initNonSystemApps(),都会扫描 system partitions、APEX 和 /data/app。差异是 scan flags 与后处理,不是“普通启动不扫描”。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期 package scan
// ... PackageManagerService 构造期已准备 packageSettings 和 InitAppsHelper。
final int[] userIds =
mUserManager.getUserIds();
PackageParser2 packageParser =
mInjector
.getScanningCachingPackageParser();
mOverlayConfig = mInitAppsHelper
.initSystemApps(
packageParser,
packageSettings,
userIds,
startTime);
mInitAppsHelper.initNonSystemApps(
packageParser,
userIds,
startTime);
packageParser.close();
// 本文注:两个入口在普通启动也执行,差异由 scanFlags 承载。
// ... 继续执行权限、app data 和 settings 后处理。InitAppsHelper 只在 first boot/upgrade 添加 SCAN_FIRST_BOOT_OR_UPGRADE;普通启动仍保留 SCAN_BOOTING | SCAN_INITIAL。
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
相关函数:InitAppsHelper.InitAppsHelper
// ... InitAppsHelper 构造函数先保存 PMS/ApexManager 等依赖。
mIsDeviceUpgrading =
mPm.isDeviceUpgrading();
int scanFlags =
SCAN_BOOTING | SCAN_INITIAL;
if (mIsDeviceUpgrading
|| mPm.isFirstBoot()) {
// 本文注:first boot 与 upgrade 只在这个 bit 上合流。
mScanFlags = scanFlags
| SCAN_FIRST_BOOT_OR_UPGRADE;
} else {
mScanFlags = scanFlags;
}
mSystemScanFlags =
mScanFlags | SCAN_AS_SYSTEM;
mExecutorService =
ParallelPackageParser
.makeExecutorService();
// ... 完成其他 scan 状态初始化。data app 目录也始终扫描;特殊 flag 额外修复目录 mode。
相关函数:InitAppsHelper.initNonSystemApps
public void initNonSystemApps(
PackageParser2 packageParser,
@NonNull int[] userIds,
long startTime) {
EventLog.writeEvent(
EventLogTags
.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
if ((mScanFlags
& SCAN_FIRST_BOOT_OR_UPGRADE)
== SCAN_FIRST_BOOT_OR_UPGRADE) {
// 本文注:修复 mode 是特殊分支,后面的 /data/app 扫描不是。
fixInstalledAppDirMode();
}
scanDirTracedLI(
mPm.getAppInstallDir(),
0,
mScanFlags | SCAN_REQUIRE_KNOWN,
packageParser,
mExecutorService,
null);
// ... 记录 data scan 耗时并执行其他收尾。
}一个重要消费者是 ABI 推导。普通启动优先复用 PackageSetting 保存的 ABI;first/upgrade 重新派生,避免系统或 native library 变化后沿用旧结果。
源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
相关函数:ScanPackageUtils.scanPackageOnly
// ... scanPackageOnly() 从 ScanRequest 取出 pkgSetting 和 scanFlags。
String primaryCpuAbiFromSettings = null;
String secondaryCpuAbiFromSettings = null;
boolean needToDeriveAbi =
(scanFlags
& SCAN_FIRST_BOOT_OR_UPGRADE)
!= 0;
// 本文注:特殊 bit 把“复用旧 ABI”改为“重新推导”。
if (!needToDeriveAbi) {
if (pkgSetting != null) {
if (pkgSetting.getPkg() != null
&& pkgSetting.getPkg().isStub()) {
needToDeriveAbi = true;
} else {
primaryCpuAbiFromSettings =
pkgSetting
.getPrimaryCpuAbiLegacy();
secondaryCpuAbiFromSettings =
pkgSetting
.getSecondaryCpuAbiLegacy();
}
} else {
// 本文注:没有 PackageSetting 时也必须重新推导 ABI。
needToDeriveAbi = true;
}
}
// ... 后续根据 needToDeriveAbi 调用 PackageAbiHelper。这个消费者说明 scan flag 不只影响目录遍历,还改变 package state 如何复用。普通启动的快路径依赖 PackageSetting 已写回的 ABI;first/upgrade 则为新的 system/native library 状态重算。下图把这个 bit 放回完整 PMS 时序。
图中的“写回”是提交点,不是扫描入口的一个副作用。在它之前崩溃,旧 fingerprint 仍在;在它之后崩溃,下次 PMS 不再进入 upgrade。
6. 首次启动动作
PMS first boot 的特有工作不仅是 scan flag:
- 通知 installd data wipe 后首次启动;
- 可选复制 system_other 预优化 artifacts;
- 初始化 default preferred apps;
- 按 user type allowlist 安装/卸载预装系统包;
- 为 boot dexopt 选择
first-bootreason。
user-type allowlist 只在 first boot 或有效 upgrade 时执行;普通启动直接返回。
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
相关函数:UserSystemPackageInstaller.installAllowlistedSystemPackages
boolean installAllowlistedSystemPackages(
boolean isFirstBoot,
boolean isUpgrade,
@Nullable ArraySet<String>
preExistingPackages) {
final int mode = getAllowlistMode();
checkAllowlistedSystemPackages(mode);
final boolean isConsideredUpgrade =
isUpgrade && !isIgnoreOtaMode(mode);
// 本文注:普通启动在这里提前返回,不会重做 user-type 收敛。
if (!isConsideredUpgrade
&& !isFirstBoot) {
return false;
}
if (isFirstBoot && !isEnforceMode(mode)) {
return false;
}
// ... 计算各 user type 的 install/uninstall 变更。首次启动应用 default preferred activity 的条件与老版本迁移合并:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期 preferred activity 初始化
// ... PackageManagerService 构造期已完成 package scan。
if (mPromoteSystemApps || mFirstBoot) {
// 本文注:first boot 与旧 schema 迁移共用同一个消费分支。
final List<UserInfo> users =
mInjector.getUserManagerInternal()
.getUsers(true);
for (int i = 0; i < users.size(); i++) {
mSettings.applyDefaultPreferredAppsLPw(
users.get(i).id);
}
}
// ... 继续处理默认 browser 和其他 user settings。mPromoteSystemApps 代表旧 database version 迁移,mFirstBoot 代表空 Settings;两者都需要为每个 user 重建默认 Intent 选择,但不意味着它们是同一种启动场景。
7. OTA后处理
OTA 分支在 package scan 后通知 PermissionManager 重新处理 internal storage 权限,并清除 app code cache。源码明确保留 ART profiles,供 OTA 后 verification 与 profile compilation 使用。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期权限恢复
// ... package scan 完成后取得 internal VersionInfo。
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,
// 本文注:PermissionManager 消费的是真实 mIsUpgrade,不是 mock flag。
mIsUpgrade);
ver.sdkVersion = mSdkVersion;
ver.sdkVersionFull = mSdkVersionFull;
// ... 继续处理 database migration。这里的 owner 仍是 PMS,PermissionManager 只消费一次 mount 通知中的 mIsUpgrade。注意 persist.pm.mock-upgrade 能让 isDeviceUpgrading() 为 true,但不会把这个参数改成 true。
相关位置:PackageManagerService 构造期 code cache 清理
// ... 权限与 package state 迁移完成后进入 code-cache 清理。
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())) {
mAppDataHelper.clearAppDataLIF(
ps.getPkg(), USER_ALL,
// 本文注:只清 code cache,显式保留 ART profiles。
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;
}
// ... 继续 settings commit。新 fingerprint 在本轮末尾随 settings 写回,所以正常情况下下一次重启不再进入 upgrade。若 system_server 在写回前崩溃,runtime restart 后仍会检测到旧 fingerprint,再次执行 upgrade 分支;这些状态因此不能互相替代。
8. Boot dexopt
SystemServer 在其他服务启动阶段调用 updatePackagesIfNeeded(),外层暂停当前 Watchdog checker,因为这项同步工作可能持续 30 秒到数分钟。pause 不会让任务异步,也不是性能优化。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关函数:SystemServer.startOtherServices
// ... startOtherServices() 已初始化 ART Service 本地管理器。
t.traceBegin("UpdatePackagesIfNeeded");
try {
// 本文注:pause 只暂停当前 checker 的超时判定,调用仍同步。
Watchdog.getInstance()
.pauseWatchingCurrentThread("dexopt");
mPackageManagerService
.updatePackagesIfNeeded();
} catch (Throwable e) {
reportWtf("update packages", e);
} finally {
Watchdog.getInstance()
.resumeWatchingCurrentThread("dexopt");
}
t.traceEnd();
// ... 继续 update metrics 和 fstrim。DexOptHelper 按优先级选择 reason:first boot 优先,然后 OTA,再检查 boot classpath APEX 是否变化;都不满足就直接返回。
源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java
相关函数:DexOptHelper.performPackageDexOptUpgradeIfNeeded
public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
String reason;
// 本文注:这个 else-if 顺序就是 reason 优先级。
if (mPm.isFirstBoot()) {
reason =
ReasonMapping.REASON_FIRST_BOOT;
} else if (mPm.isDeviceUpgrading()) {
reason =
ReasonMapping.REASON_BOOT_AFTER_OTA;
} else if (hasBcpApexesChanged()) {
reason = ReasonMapping
.REASON_BOOT_AFTER_MAINLINE_UPDATE;
} else {
return;
}
Log.i(TAG,
"Starting boot dexopt for reason "
+ reason);
getArtManagerLocal().onBoot(
reason,
null,
null);
}Mainline 判据遍历 active APEX,只要 boot classpath 中某个 APEX 的 activeApexChanged 为 true 就触发。因此 partition fingerprint 不变也可能执行 boot dexopt。
ART Service 的 onBoot 不是简单全量 dex2oat。OTA/Mainline 路径先检查 pre-reboot staged artifacts;可提交时先提交 primary dex artifacts,secondary dex 因 CE storage 尚未解密留到 boot complete 后。
源码文件:art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
相关函数:ArtManagerLocal.onBoot
// ... onBoot() 已确认 reason 是 OTA 或 Mainline,并创建 stats session。
// 本文注:该分支只属于 OTA/Mainline reason,first-boot 不检查 staged files。
PreRebootStagedFilesStatus status =
mInjector.getArtd()
.checkPreRebootStagedFilesStatus();
if (status == null) {
statsAfterRebootSession
.recordArtifactsEndStatus(
PreRebootStatsReporter
.END_STATUS_MISSING,
0 /* ageMillis */);
} else if (!status.isCommittable) {
// 本文注:不匹配当前 build/APEX 的产物不能提交,直接清理。
statsAfterRebootSession
.recordArtifactsEndStatus(
PreRebootStatsReporter
.END_STATUS_OBSOLETE,
mInjector.getClock()
.currentTimeMillis()
- status.createdAtMillis);
mInjector.getArtd()
.cleanUpPreRebootStagedFiles();
} else {
// 本文注:primary 现在提交,secondary 由 BOOT_COMPLETED 回调再提交。
mShouldCommitPreRebootStagedFiles = true;
mStatsAfterRebootSession =
statsAfterRebootSession;
mInjector.getArtd()
.deletePreRebootStagedMetadata();
commitPreRebootStagedFiles(
snapshot,
false /* forSecondary */);
}
// ... catch ServiceSpecificException/RemoteException,并异步上报 stats。检查 staged files 的 ServiceSpecificException 或 RemoteException 被记为 error,但 catch 的作用域不包含后面的 dexoptPackages()。因此“预重启产物无法复用”会降级为开机期常规 dexopt,而不是直接跳过优化。
// 本文注:staged files 分支和异常处理结束后,仍同步进入 dexoptPackages。
dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor,
progressCallback != null ? Map.of(ArtFlags.PASS_MAIN, progressCallback) : null);dexoptPackages() 再调用 reason selector 取默认 package 列表。基础 stream 已经排除不能 dexopt 和已休眠的 package;first boot 不再做 inactive 过滤,Mainline update 只保留 SystemUI/Launcher,OTA 走 default 分支,过滤超过 inactive threshold 的 package 后再按 score 与最近活跃时间排序。
源码文件:art/libartservice/service/java/com/android/server/art/ReasonMapping.java
相关函数:ReasonMapping.getDefaultPackagesForReason
// ... 基础 stream 已过滤不可 dexopt 和 hibernating package,并计算 thresholdTimeMs。
packages = switch (reason) {
case ReasonMapping
.REASON_BOOT_AFTER_MAINLINE_UPDATE ->
packages.filter(pkgInfo ->
mInjector.isSystemUiPackage(
pkgInfo.pkgState()
.getPackageName())
|| mInjector.isLauncherPackage(
pkgInfo.pkgState()
.getPackageName()));
// ... REASON_INACTIVE 使用独立的阈值与升序排序分支。
case ReasonMapping.REASON_FIRST_BOOT ->
packages;
default -> {
// 本文注:OTA 在该分支;它不是无条件全量编译。
Comparator<PackageInfo> comparator =
Comparator
.<PackageInfo>comparingDouble(
PackageInfo::score)
.thenComparingLong(
PackageInfo::lastActiveTime)
.reversed();
yield packages
.filter(pkgInfo ->
pkgInfo.lastActiveTime()
> thresholdTimeMs)
.sorted(comparator);
}
};这段选择逻辑是“要处理哪些 package”,不是“一定生成什么 compiler artifact”;后者还受现有 dexopt 状态、compiler filter 和 profile 影响。下图按真实调用顺序区分 reason 判定、staged files 恢复与 package 选择。
这条时序的关键是两个屏障:onBoot() 是同步调用,所以 SystemServer 在 dexopt 完成前不会越过它;secondary staged files 却故意延迟到 BOOT_COMPLETED,因为当前阶段 CE storage 不可用。
9. 状态消费者
SystemServer 从 PMS 读取 mFirstBoot,但用途有限。创建 WMS 时传入 !mFirstBoot 作为 showBootMsgs,控制 mAllowBootMessages;这不是是否显示 BootAnimation。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关函数:SystemServer.startOtherServices
// ... startOtherServices() 已启动 PMS、IMS 和显示依赖。
mFirstBoot =
mPackageManagerService.isFirstBoot();
// 本文注:WMS 收到的是 showBootMsgs=!mFirstBoot,不是 BootAnimation 开关。
wm = WindowManagerService.main(
context,
inputManager,
!mFirstBoot,
new PhoneWindowManager(),
mActivityManagerService
.mActivityTaskManager);
// ... 注册 WMS Binder service 并调用 onInitReady()。另一个重要消费者是 metrics。SYSTEM_SERVER_READY 只在非 runtime restart、非 PMS first boot、非 upgrade时上报,并对超过 60 秒记录 wtf。这实际上把指标限定在 steady-state normal boot。
相关函数:SystemServer.run
// ... SystemServer.run() 已完成所有 system service 启动。
// 本文注:事件缺失可能是条件性抑制,不代表 SystemServer 没到 ready。
if (!mRuntimeRestart
&& !isFirstBootOrUpgrade()) {
final long uptimeMillis =
SystemClock.elapsedRealtime();
FrameworkStatsLog.write(
FrameworkStatsLog
.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
FrameworkStatsLog
.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
uptimeMillis);
final long maxUptimeMillis =
60 * 1000;
if (uptimeMillis > maxUptimeMillis) {
Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
"SystemServer init took too long. uptimeMillis="
+ uptimeMillis);
}
}
// ... 继续注册 Binder transaction 错误回调。用 stats 分析 OTA 或 first boot 时,如果事件缺失,不应解释为代码没有走到 ready;它可能是条件性抑制。EventLog boot_progress_* 仍是更直接的时间线。
10. 写回边界
upgrade 分支只有在扫描、权限、code cache 和其他迁移完成后才更新 VersionInfo 并写 settings。databaseVersion 也在所有 scan change 完成后推进。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
相关位置:PackageManagerService 构造期 settings commit
// ... package/permission/code-cache 迁移已完成。
// 本文注:先推进 databaseVersion,再与 fingerprint/package state 一起写回。
ver.databaseVersion =
Settings.CURRENT_DATABASE_VERSION;
t.traceBegin("write settings");
writeSettingsLPrTEMP();
t.traceEnd();
EventLog.writeEvent(
EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
// ... 构造函数继续初始化运行期组件。成功的写回是“升级已消费”的提交点。此前失败会使下次 PMS 仍看到旧 fingerprint 并重跑迁移;写回成功后再重启,mIsUpgrade=false。这比依赖一次性 property 更能恢复中断。
但 writeSettingsLPrTEMP() 没有把写入失败抛回构造函数。Settings.writeLPr() 捕获 IOException,记录“changes will be lost at reboot”并清理损坏的主文件;调用方仍会继续写 BOOT_PROGRESS_PMS_READY。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java
相关函数:Settings.writeLPr
try {
// ... serializer 已写入 VersionInfo、package、permission 和 keyset。
atomicFile.finishWrite(str);
writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr(sync);
writeAllRuntimePermissionsLPr();
return;
} catch (java.io.IOException e) {
// 本文注:失败被降级为 wtf,PMS 不会因此终止启动。
Slog.wtf(PackageManagerService.TAG,
"Unable to write package manager settings, current changes will be lost at reboot",
e);
if (str != null) {
atomicFile.failWrite(str);
}
}
// ... writeLPr() 返回调用方。因此严格的提交条件是 finishWrite() 成功,不是看到 BOOT_PROGRESS_PMS_READY。若写入失败,当前进程仍保留新 VersionInfo,但下次启动会从旧持久化状态重新判定。
首次启动也类似:packages.xml 生成并写回后,下次启动 readLPw() 返回 true,mFirstBoot=false。因此 first boot 是 PMS 当前初始化周期的状态,不是永久设备属性。
11. 验证方法
InitAppsHelperTest 直接覆盖四种关键输入:无现有 package 表示 first boot;mock OTA;fingerprint 未变的普通启动;fingerprint 改变的 upgrade。每个测试同时断言 PMS 两个布尔值与 SCAN_FIRST_BOOT_OR_UPGRADE bit。
源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt
相关测试:testSystemScanFlagOnFirstBoot
@Test
fun testSystemScanFlagOnFirstBoot() {
// 本文注:不预置 package settings,这是 first-boot 的 arrange。
val pms = createPackageManagerService()
assertThat(pms.isFirstBoot).isEqualTo(true)
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(PackageManagerService
.SCAN_FIRST_BOOT_OR_UPGRADE)
}这个用例的 action 是创建 PMS 和 InitAppsHelper;assert 同时要求 isFirstBoot=true、isDeviceUpgrading=false 且 scan bit 置位。它特别反驳“空 data 一定也是 OTA”。
相关测试:testSystemScanFlagNoOTA
@Test
fun testSystemScanFlagNoOTA() {
// 本文注:同时模拟 settings 存在与 fingerprint 不变。
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)
}这组测试证明判据和 scan flag,不证明真实目录 I/O、权限迁移或 dexopt 时间。
ART 的 testOnBoot() 为 DexoptHelper 配置一个只匹配 first-boot reason 的 stub,再调用 onBoot(first-boot);OTA staged artifact 测试则显式验证 primary dex artifacts 被提交、metadata 被删除。它们分别覆盖 reason 传递约束和 pre-reboot artifact 消费。
源码文件:art/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
相关测试:ArtManagerLocalTest.testOnBoot
// ... testOnBoot() 先创建 progressCallbackExecutor 和 progressCallback。
// 本文注:只有 DexoptParams.reason 保持 first-boot,mock 才能匹配这次调用。
when(mDexoptHelper.dexopt(
any(), any(),
argThat(params -> params.getReason()
.equals(ReasonMapping
.REASON_FIRST_BOOT)),
any(), any(),
same(progressCallbackExecutor),
same(progressCallback)))
.thenReturn(DexoptResult.create());
mArtManagerLocal.onBoot(
ReasonMapping.REASON_FIRST_BOOT,
progressCallbackExecutor,
progressCallback);argThat 是 stub 的匹配约束,不是一条独立 verify()。这个用例能检查当前 mock harness 下的 reason 传递,但它的证明强度弱于显式验证调用次数和完整参数。它也不断言实际 compiler filter、artifact 路径或执行耗时。
testCommitPreRebootStagedFiles() 提供了另一组输入:isCommittable=true。它先断言 deletePreRebootStagedMetadata() 和 primary commit 的顺序,再模拟 ACTION_BOOT_COMPLETED,断言 secondary commit 是第二次发生;testCommitPreRebootStagedFilesObsolete() 则以 isCommittable=false 断言 cleanup 且从未 commit。这两个用例共同覆盖成功、延迟和废弃路径。
相关测试:ArtManagerLocalTest.testCommitPreRebootStagedFiles
when(mArtd.checkPreRebootStagedFilesStatus())
.thenReturn(TestingUtils.createPreRebootStagedFilesStatus(
true /* isCommittable */, 200 /* createdAtMillis */));
mArtManagerLocal.onBoot(ReasonMapping.REASON_BOOT_AFTER_OTA,
null /* progressCallbackExecutor */, null /* progressCallback */);
InOrder inOrder = inOrder(mArtd);
inOrder.verify(mArtd).deletePreRebootStagedMetadata();
inOrder.verify(mArtd).commitPreRebootStagedFiles(
inAnyOrderDeepEquals(
AidlUtils.buildArtifactsPathAsInput(
"/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache),
AidlUtils.buildArtifactsPathAsInput(
"/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache),
AidlUtils.buildArtifactsPathAsInput(
"/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache),
AidlUtils.buildArtifactsPathAsInput(
"/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)),
inAnyOrderDeepEquals(
AidlUtils.toWritableProfilePath(
AidlUtils.buildProfilePathForPrimaryRefAsInput(
PKG_NAME_1, "primary")),
AidlUtils.toWritableProfilePath(
AidlUtils.buildProfilePathForPrimaryRefAsInput(
PKG_NAME_1, "split_0.split"))));
// 本文注:开机期只允许第一次 primary commit。
verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any());
mArtManagerLocal.systemReady();
verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any());
simulateBroadcast(Intent.ACTION_BOOT_COMPLETED);
// 本文注:BOOT_COMPLETED 解锁 secondary commit,总次数变为 2。
verify(mArtd, times(2)).commitPreRebootStagedFiles(any(), any());这里 systemReady() 本身不提交 secondary files,真正的消费事件是随后的 ACTION_BOOT_COMPLETED。因此测试不只验证“调了两次”,还验证了两次提交之间的生命周期屏障。
设备只读诊断应同时读取四类状态,而不是只看一个属性:
# 输入:当前system_server。输出:本次物理boot内的restart次数和时钟锚点。
adb shell dumpsys system_server_dumper \
--name SystemServer
# 输入:property service。输出:boot完成状态、SystemServer计数和BootReceiver时间戳。
adb shell getprop sys.boot_completed
adb shell getprop sys.system_server.start_count
adb shell getprop ro.runtime.firstboot
# 输入:PMS/ART日志。输出:settings缺失、fingerprint升级、scan和boot dexopt reason。
adb logcat -b system -v threadtime -d \
| rg 'First Boot|Upgrading from|Build fingerprint changed|Starting boot dexopt|onBoot: reason'
# 输入:源码树。输出:所有firstBoot/upgrading消费者,检查改动blast radius。
rg -n 'isFirstBoot\(|isDeviceUpgrading\(|mRuntimeRestart|ro.runtime.firstboot' \
frameworks/base/services \
frameworks/base/core/java/com/android/internal/os/ZygoteInit.javapersist.pm.mock-upgrade 是测试/调试开关,若被遗留为 true,会在 fingerprint 未变时仍让 isDeviceUpgrading() 返回 true。排查异常 OTA 分支时必须一起读取。
检查自己是否读通这篇文章,可以回答两个问题:普通冷启动为什么 mRuntimeRestart=false 但 mFirstBoot 也为 false;以及 OTA scan 中 system_server 崩溃后,新的进程为什么同时可能是 runtime restart 和 device upgrading。能把 property、packages.xml、VersionInfo 与 ART reason 分开后,启动数据才可以跨场景比较。
