19、OTA升级与AB:AB分区、AB与动态分区共存

聊到OTA升级,就绕不开AB分区。说实话,我第一次接触AB分区时,脑子里就一个想法——这不就是给系统上了个“双保险”嘛。但真正深入进去才发现,里面的门道比想象中要多得多。

今天我们就来拆解一下AB分区机制,以及它和动态分区怎么和平共处。

什么是AB分区?

AB分区,也叫无缝升级(Seamless Update)。它的核心思想很简单:

系统准备两套分区槽位——Slot A 和 Slot B。一套在运行,另一套就闲着。升级时,把新系统写入空闲的那套分区。写完后,下次重启切过去就行。

我打个比方:就像你有两套房子,一套住着,另一套装修。装修完了,搬过去住,原来的房子再翻新。这样你永远有地方住,不会露宿街头。

AB分区的核心优势:

  • 升级失败不影响当前系统
  • 用户几乎感知不到升级过程
  • 可以随时回滚到旧版本

AB分区的工作原理

咱们来看看具体流程。我习惯把AB升级分成三个阶段:

  1. 写入阶段:系统在后台把新固件写入空闲槽位。用户该干嘛干嘛,完全无感。
  2. 切换阶段:写入完成后,设置一个“启动标记”,告诉Bootloader下次从新槽位启动。
  3. 生效阶段:重启后,系统从新槽位启动。如果启动成功,旧槽位就变成“待更新”状态。

这里有个关键点——Bootloader怎么知道该从哪个槽位启动?

答案是:靠misc分区里的一个叫“bootctrl”的元数据。它记录了当前活跃槽位、槽位状态、是否成功启动等信息。

我个人习惯:在调试AB升级时,先用 adb shell getprop ro.boot.slot_suffix 查看当前槽位。这能帮你快速定位问题。

AB分区与动态分区共存

好,问题来了。动态分区把system、vendor这些分区都合并成了一个super分区。那AB分区怎么和它共存?

嗯,这里要分清楚:AB分区管的是“槽位”,动态分区管的是“分区布局”。两者是不同维度的东西。

共存的方式其实很直接:

  • 每个槽位都有自己的super分区。比如 super_asuper_b
  • 每个super分区内部,再通过动态分区管理子分区。
  • 升级时,把整个super分区的内容写入空闲槽位。

说白了,就是“双份的super分区”。

分区布局示例:

boot_a
boot_b
super_a  (包含 system_a, vendor_a, product_a ...)
super_b  (包含 system_b, vendor_b, product_b ...)
misc
userdata
...

你看,每个槽位都有自己的boot分区和super分区。这样升级时,Bootloader只需要知道切到哪个槽位,剩下的由动态分区自己搞定。

我踩过的坑

我曾经在一个项目里遇到过一个诡异的问题:升级后设备反复重启,就是进不了系统。

排查了半天,发现是super分区的大小没对齐。两个槽位的super分区大小不一致,导致写入时数据被截断了。

避坑指南:

  • 两个槽位的super分区大小必须完全一致
  • 分区表里要预留足够的空间给两个super分区
  • 动态分区的metadata要放在super分区内部,不要依赖外部存储

AB升级的完整流程

咱们用一张图来梳理整个流程。我画了个SVG,把关键步骤都标出来了。

AB分区 + 动态分区 OTA升级流程 当前状态 Slot A 运行中 下载更新包 OTA包下载到cache 写入Slot B super_b + boot_b 设置启动标记 misc分区写入 bootctrl 重启设备 Bootloader读取槽位标记 启动成功 ✅ Slot B 成为新活跃槽位 启动失败 ❌ 回滚到 Slot A 正常流程 关键操作

从图里你能看到,整个流程是线性的。但有个关键点——如果启动失败,Bootloader会自动回滚到旧槽位。这就是AB分区最牛的地方:永远有退路。

动态分区下的AB升级细节

在动态分区环境下,AB升级有几个特殊之处:

特性 说明
super分区大小 两个槽位的super分区必须大小一致,否则写入会失败
metadata管理 每个super分区有自己的metadata,互不干扰
逻辑分区映射 升级时,逻辑分区(如system、vendor)在super内部重新分配
回滚策略 回滚时,只需切换槽位,super分区内容不变

注意:动态分区下的AB升级,对分区表的要求更严格。我曾经遇到过因为super分区预留空间不足,导致升级包无法写入的情况。建议super分区大小至少是实际内容的1.2倍。

实际项目中的经验

我记得有个项目,客户要求实现“零 downtime”升级。我们用了AB分区+动态分区的方案,效果确实不错。

但有个细节让我印象深刻:升级完成后,旧槽位的数据怎么处理?

答案是:不处理。旧槽位的数据会保留,直到下一次升级时被覆盖。这样做的目的是:如果新系统有问题,用户可以随时回滚。

你想想看,如果升级完就把旧槽位清空了,那回滚功能就废了。所以,保留旧数据不是浪费空间,而是为了安全。

总结一下

AB分区和动态分区,说白了就是两个互补的技术。AB分区管“槽位切换”,动态分区管“空间管理”。两者结合,既能实现无缝升级,又能灵活分配分区空间。

我个人觉得,这套方案是目前Android OTA升级的最优解。虽然实现起来复杂一些,但带来的用户体验提升是实实在在的。

嗯,今天就聊到这里。如果你在实际项目中遇到AB分区的问题,欢迎交流。


公众号:蓝海资料掘金营,微信deep3321