非 A/B 系统更新

在老款的没有 A/B 分区的 Android 设备上,闪存空间通常包含以下分区:

boot
包含 Linux 内核和最小的根文件系统(加载到 RAM 磁盘)。它装载了系统和其他分区,并启动位于 system 分区上的运行时。
system
包含在 Android 开源项目 (AOSP) 上提供源代码的系统应用和库。在正常操作期间,此分区被装载为只读分区;其内容仅在 OTA 更新期间更改。
vendor
包含在 Android 开源项目 (AOSP) 上未提供源代码的系统应用和库。在正常操作期间,此分区被装载为只读分区;其内容仅在 OTA 更新期间更改。
userdata
存储由用户安装的应用所保存的数据等。OTA 更新过程通常不会触及该分区。
cache
供几个应用使用的临时保留区域(访问此分区需要使用特殊的应用权限),用于存储下载的 OTA 更新软件包。其他程序也可使用该空间,但是此类文件可能会随时消失。安装某些 OTA 软件包可能会导致此分区被完全擦除。cache 分区还包含 OTA 更新的更新日志。
recovery
包含第二个完整的 Linux 系统,其中包括一个内核和特殊的恢复二进制文件(该文件可读取一个软件包并使用其内容来更新其他分区)。
misc
执行恢复操作时使用的微小分区,可在应用 OTA 软件包并重新启动设备时,隐藏某些进程的信息。

OTA 更新过程

典型 OTA 更新包含以下步骤:

  1. 设备会定期与 OTA 服务器进行确认,并在有可用的更新时获得通知,包括更新软件包的 URL 和向用户显示的描述字符串。
  2. 将更新下载到 cache 或 userdata 分区,并根据 /system/etc/security/otacerts.zip 中的证书验证加密签名。系统提示用户安装更新。
  3. 设备重新启动进入恢复模式,引导 recovery 分区中的内核和系统(而非 boot 分区中的内核)启动。
  4. recovery 分区的二进制文件由 init 启动。它会在 /cache/recovery/command 中寻找将其指向下载软件包的命令行参数。
  5. 恢复操作会根据 /res/keys(包含在 recovery 分区中的 RAM 磁盘的一部分)中的公钥来验证软件包的加密签名。
  6. 从软件包中提取数据,并根据需要使用该数据更新 boot、system 和/或 vendor 分区。system 分区上的某个新文件包含新的 recovery 分区的内容。
  7. 设备正常重启。
    1. 加载最新更新的 boot 分区,在最新更新的 system 分区中装载并开始执行二进制文件。
    2. 作为正常启动的一部分,系统会根据所需内容(预先存储为 /system 中的一个文件)检查 recovery 分区的内容。二者内容不同,所以 recovery 分区会被所需内容重新刷写(在后续引导中,recovery 分区已经包含新内容,因此无需重新刷写)。

系统更新完成!更新日志可以在 /cache/recovery/last_log.# 中找到。

更新软件包

更新软件包是包含可执行二进制文件 META-INF/com/google/android/update-binary.zip 文件。对软件包上的签名进行验证后,recovery 会将该二进制文件解压到 /tmp 并运行该二进制文件,同时传递以下参数:

  • 更新二进制 API 版本号。如果向更新二进制文件传递的参数发生变化,此数字将递增。
  • 命令管道的文件描述符。更新程序可以使用此管道将命令发送回恢复二进制文件,主要用于界面变化,例如向用户指示进度。
  • 更新软件包 .zip 文件的文件名

更新软件包可以使用任何静态链接的二进制文件作为更新二进制文件。OTA 软件包构建工具使用更新程序 (bootable/recovery/updater),该程序提供了一种可以执行很多安装任务的简单脚本语言。您可以替换设备上运行的任何其他二进制文件。

如需详细了解更新程序二进制文件、edify 语法和内建函数,请参阅 OTA 软件包内部探秘

从更早版本迁移

当从 Android 2.3/3.0/4.0 版本进行迁移时,主要变化是将设备专属的功能从一组具有预定义名称的 C 函数转换为 C++ 对象。下表列出了用途大致相同的旧函数和新方法:

C 函数 C ++ 方法
device_recovery_start() Device::RecoveryStart()
device_toggle_display()
device_reboot_now()
RecoveryUI::CheckKey()
(也称为 RecoveryUI::IsKeyPressed())
device_handle_key() Device::HandleMenuKey()
device_perform_action() Device::InvokeMenuItem()
device_wipe_data() Device::WipeData()
device_ui_init() ScreenRecoveryUI::Init()

将旧函数转化为新方法应尽量简单直观。不要忘记添加新的 make_device() 函数来创建并返回新设备子类的实例。