非 A/B 系统更新

非 AB 更新是旧版 Android 设备(Android 6 及更早版本)使用的已弃用 OTA 方法。这些设备具有专用的恢复分区,其中包含解压缩下载的更新软件包并将更新应用到其他分区所需的软件。

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

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

OTA 更新的生命周期

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

  1. 设备执行与 OTA 服务器的定期签到,并收到更新可用性的通知,包括更新软件包的网址和向用户显示的描述字符串。
  2. 更新下载到缓存或数据分区,并根据 /system/etc/security/otacerts.zip 中的证书验证其加密签名。系统会提示用户安装更新。
  3. 设备重启进入恢复模式,在该模式下,恢复分区中的内核和系统会启动,而不是启动分区中的内核。
  4. 恢复二进制文件由 init 启动。它在 /cache/recovery/command 中找到命令行参数,这些参数指向下载的软件包。
  5. 恢复会针对 /res/keys 中的公钥(恢复分区中包含的 RAM 磁盘的一部分)验证软件包的加密签名。
  6. 从软件包中提取数据,并根据需要用于更新启动分区、系统分区和/或供应商分区。留在系统分区上的新文件之一包含新恢复分区的内容。
  7. 设备正常重启。
    1. 新更新的启动分区被加载,它挂载并开始执行新更新的系统分区中的二进制文件。
    2. 作为正常启动的一部分,系统会将恢复分区的内容与所需内容(之前存储为 /system 中的文件)进行检查。如果它们不同,则会使用所需内容重新刷写恢复分区。(在后续启动中,恢复分区已包含新内容,因此无需重新刷写。)

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

更新软件包

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

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

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

有关 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() 函数来创建并返回新的 Device 子类的实例。