Skip to content

启动场景判定

区分普通冷启动、data 首次启动、OTA/Mainline 升级与 runtime restart,并追踪扫描、迁移和 boot dexopt 分支。

基于android-17.0.0_r1
AndroidSystemServerPackageManagerARTOTARuntimeRestart源码阅读

启动场景判定 ​

本文面向已经读过 启动时间分析、预加载优化、开机动画时序 和 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 markerZygoteInitsys.boot_completed == 1当前物理 bootStatsLog 抑制
SystemServer restartSystemServerstart_count > 1当前物理 bootdump、metrics、启动分支
PMS first bootPackageManager首次 openRead() 无 current/backup/reserve streamdata 分区状态installd、scan、默认配置、dexopt
PMS upgradingPackageManagerpartition fingerprint 变化首次写入新 fingerprint 前scan、权限、cache、dexopt

同一设备可能同时满足多个维度。例如 OTA 应用后、system_server 在 package scan 中崩溃并重启:第二个 SystemServer 是 runtime restart,PMS 仍可能认为 upgrading;sys.boot_completed 仍为空,Zygote 的局部统计标记却不一定认为它是 restart。

常见场景矩阵如下:

场景runtime restartPMS first bootPMS upgradingboot 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

java
// 本文注:这是 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

java
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

java
// ... 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

java
@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

java
@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

java
// ... 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

java
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

java
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

java
// ... 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

java
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 构造期首次启动分支

java
// ... 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

java
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 构造期版本判定

java
// ... 从 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

java
// ... 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

java
// ... 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

java
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

java
// ... 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-boot reason。

user-type allowlist 只在 first boot 或有效 upgrade 时执行;普通启动直接返回。

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

相关函数:UserSystemPackageInstaller.installAllowlistedSystemPackages

java
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 初始化

java
// ... 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 构造期权限恢复

java
// ... 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 清理

java
// ... 权限与 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

java
// ... 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

java
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

java
// ... 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,而不是直接跳过优化。

java
// 本文注: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

java
// ... 基础 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

java
// ... 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

java
// ... 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

java
// ... 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

java
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

kotlin
@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

kotlin
@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

java
// ... 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

java
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。因此测试不只验证“调了两次”,还验证了两次提交之间的生命周期屏障。

设备只读诊断应同时读取四类状态,而不是只看一个属性:

bash
# 输入:当前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.java

persist.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 分开后,启动数据才可以跨场景比较。