安卓手机重启的 "工厂停工复产" 故事

56 阅读5分钟

一、总控室收到停工指令

假设安卓手机是一个大型智能工厂,PowerManager 就是工厂的总控室。当你按下电源键选择 "重启" 时,就像总控室接到了 "全厂停工后重新启动" 的命令。

java

// 总控室接到重启指令
public void reboot(String reason) {
    // 通过对讲机通知底层控制中心
    mService.reboot(false, reason, true);
}

二、各部门停工前的准备

总控室的工作人员(PowerManagerService)接到指令后,开始安排各部门有序停工:

java

// 总控室调度员处理重启请求
public void reboot(boolean confirm, String reason, boolean wait) {
    // 检查是否有停工权限
    mContext.enforceCallingOrSelfPermission(Manifest.permission.REBOOT, null);
    // 启动停工协调流程
    shutdownOrRebootInternal(false, confirm, reason, wait);
}

这里有三个关键参数:

  • shutdown=false:不是彻底停工(关机),而是重启
  • confirm=false:不需要弹窗确认,直接执行
  • wait=true:总控室需要等待所有部门完成停工

三、停工协调小组开始工作

总控室派出了专门的停工协调小组(ShutdownThread),这个小组负责指挥各部门按顺序停工:

java

// 停工协调小组核心工作流程
public static void reboot(Context context, String reason, boolean confirm) {
    mReboot = true; // 标记为重启而非关机
    mReason = reason;
    // 开始内部停工协调
    shutdownInner(context, confirm);
}

static void shutdownInner(Context context, boolean confirm) {
    if (confirm) {
        // 需要用户确认的情况(这里不涉及)
    } else {
        // 直接开始停工流程
        beginShutdownSequence(context);
    }
}

四、拉起停工进度显示

协调小组先拉起一个进度显示窗口,就像工厂门口的电子屏显示停工进度:

java

private static void beginShutdownSequence(Context context) {
    // 创建进度显示对话框
    ProgressDialog pd = new ProgressDialog(context);
    if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
        // 恢复模式重启的特殊显示
        pd.setTitle("准备进入恢复模式");
    } else {
        // 常规重启显示
        pd.setTitle("正在关机");
        pd.setMessage("正在保存数据...");
    }
    pd.show(); // 显示进度窗口
    // 确保工厂设备不会中途休眠
    acquireWakeLocks();
    // 启动协调小组工作线程
    sInstance.start();
}

五、各部门按顺序停工

1. 发送停工广播(全厂通知)

java

public void run() {
    // 设置停工属性(记录重启原因)
    SystemProperties.set("sys.shutdown.requested", reason);
    
    // 1. 发送停工广播(通知所有应用准备关闭)
    Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
    mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, br, mHandler, 0, null, null);
    
    // 等待广播接收方回应,最多10秒
    waitForBroadcastResponse();

这一步就像工厂广播通知所有车间 "10 分钟后开始停工,请做好数据保存"。

2. 应用管理部(AMS)停工

java

// 应用管理部停工流程
public boolean shutdown(int timeout) {
    mShuttingDown = true;
    // 停止接收新任务
    updateEventDispatchingLocked();
    // 各应用车间有序停工
    timedout = mStackSupervisor.shutdownLocked(timeout);
    // 关闭统计服务
    mBatteryStatsService.shutdown();
    return timedout;
}

AMS 就像工厂的生产调度部门,负责通知各个应用车间(App)停止接收新任务,保存当前工作状态。

3. 包管理部(PMS)停工

java

// 包管理部停工时保存应用使用记录
public void shutdown() {
    mPackageUsage.write(true); // 强制保存数据
}

private void writeInternal() {
    // 打开数据文件(/data/system/package-usage.list)
    AtomicFile file = getFile();
    FileOutputStream f = file.startWrite();
    // 写入每个应用的最后使用时间
    for (PackageParser.Package pkg : mPackages.values()) {
        sb.append(pkg.packageName).append(' ').append(pkg.mLastPackageUsageTimeInMills).append('\n');
    }
    // 确保数据写入磁盘
    file.finishWrite(f);
}

PMS 就像工厂的物资管理部门,停工前会记录每个车间(应用)的最后工作时间,以便重启后恢复状态。

4. 射频设备部(NFC / 蓝牙 / 4G)停工

java

// 关闭射频设备的专门线程
private void shutdownRadios(final int timeout) {
    Thread t = new Thread() {
        public void run() {
            // 关闭NFC
            if (nfc != null && nfc.getState() != STATE_OFF) nfc.disable(false);
            // 关闭蓝牙
            if (bluetooth != null && bluetooth.isEnabled()) bluetooth.disable(false);
            // 关闭4G网络
            if (phone != null && !phone.needMobileRadioShutdown()) 
                phone.shutdownMobileRadios();
            
            // 每隔500ms检查是否全部关闭,最多等12秒
            waitForRadioShutdownComplete();
        }
    };
    t.start();
    t.join(timeout);
}

这一步就像关闭工厂里的无线通信设备(如对讲机、无线传感器),避免停工期间出现信号干扰。

5. 存储管理部(MountService)停工

java

// 存储管理部向底层存储系统发送停工命令
public void shutdown(IMountShutdownObserver observer) {
    // 发送停工消息到工作线程
    mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
}

// 工作线程处理停工消息
class MountServiceHandler extends Handler {
    public void handleMessage(Message msg) {
        if (msg.what == H_SHUTDOWN) {
            // 向底层存储系统(vold)发送shutdown命令
            boolean success = mConnector.execute("volume", "shutdown").isClassOk();
            // 通知协调小组停工完成
            obs.onShutDownComplete(success ? 0 : -1);
        }
    }
}

MountService 就像工厂的仓库管理员,停工前会确保所有文件存储操作完成,避免数据丢失。

六、总控室下达最终重启命令

当所有部门都完成停工准备后,总控室向工厂底层控制系统发送重启命令:

java

public static void rebootOrShutdown(Context context, boolean reboot, String reason) {
    if (reboot) {
        Log.i(TAG, "Rebooting, reason: " + reason);
        // 关键!向底层发送重启命令
        PowerManagerService.lowLevelReboot(reason);
    } else {
        // 关机流程(这里不涉及)
    }
}

public static void lowLevelReboot(String reason) {
    if (reason.equals("recovery")) {
        // 进入恢复模式的特殊命令
        SystemProperties.set("ctl.start", "pre-recovery");
    } else {
        // 常规重启命令
        SystemProperties.set("sys.powerctl", "reboot," + reason);
    }
    // 等待20秒让底层处理
    Thread.sleep(20000);
}

sys.powerctl=reboot这条命令就像总控室按下了工厂的 "重启按钮",底层控制系统(Linux 内核)收到后会执行真正的硬件重启操作。

七、故事总结

整个重启流程可以概括为:

  1. 用户按下重启按钮,总控室(PowerManager)收到指令

  2. 停工协调小组(ShutdownThread)拉起进度显示,开始指挥各部门停工

  3. 依次关闭应用管理部(AMS)、包管理部(PMS)、射频设备、存储管理部

  4. 所有部门准备就绪后,总控室向底层发送重启命令(sys.powerctl=reboot)

  5. 底层系统执行硬件重启,就像工厂切断电源后重新启动

如果用 adb 命令模拟这个过程,就是:

plaintext

adb shell setprop sys.powerctl reboot

这条命令相当于直接在总控室按下了重启按钮,跳过了前面的部门停工流程(实际系统中仍会按流程执行)。

后续故事将继续讲解:当总控室下达重启命令后,底层工厂(Linux 内核)是如何执行具体的硬件重启操作的,以及不同重启原因(如恢复模式)会有什么不同处理。