24、OTA升级与retrofit:非动态分区设备升级到动态分区
说实话,这个题目我当年第一次看到时,心里是有点打鼓的。你想啊,一台已经跑得好好的非动态分区设备,要通过OTA升级变成动态分区——这就像给正在飞行的飞机换引擎。但Android团队确实把这个事情做成了,而且做得还挺优雅。
今天我们就来聊聊这个"retrofit"机制。它不是什么新框架,而是Google在Android 10之后引入的一套升级策略。说白了,就是让老设备也能用上动态分区。
为什么需要retrofit?
先说说背景。动态分区的好处我们前面讲过很多了——分区大小可以动态调整,OTA包体积更小,系统更新更灵活。但问题是,很多存量设备出厂时还是传统的静态分区布局。这些设备如果要升级到支持动态分区的新系统,怎么办?
你可能会想:直接刷机不就行了?嗯,理论上可以,但OTA升级讲究的是无缝、安全、可回滚。你不能让用户插数据线、进fastboot模式去手动刷分区表。所以Google搞出了retrofit方案。
核心思路:在OTA升级过程中,动态地将原有的静态分区布局转换为动态分区布局。整个过程对用户透明,不需要额外操作。
retrofit的工作原理
我在项目中第一次接触retrofit时,花了整整两天才把整个流程理清楚。其实它的核心逻辑并不复杂,我画了一张图帮你理解:
retrofit的两种模式
根据我的经验,retrofit其实有两种实现方式。我最早接触的是第一种,后来在另一个项目里才遇到第二种。它们各有适用场景。
| 模式 | 说明 | 适用场景 | 风险等级 |
|---|---|---|---|
| 直接retrofit | 在升级过程中,直接将原有物理分区映射为super分区内的动态分区 | 分区大小足够,不需要重新划分 | 低 |
| 重建retrofit | 先创建super分区,然后将原分区数据拷贝到新位置 | 需要调整分区大小或布局 | 中 |
直接retrofit比较简单。它做的事情就是把原来的system、vendor这些物理分区,直接"装进"super分区的逻辑框架里。说白了,就是换了个管理方式,数据本身没动。
重建retrofit就复杂一些。我记得有一次,客户设备上原来的vendor分区太小,新系统需要更大的vendor空间。这时候就必须走重建流程——先创建super分区,然后把数据搬过去,最后调整大小。
代码层面的实现
我们来看看实际代码中retrofit是怎么触发的。在update_engine中,有一个关键的判断逻辑:
// 来自 update_engine 的 retrofit 检测逻辑
bool IsRetrofitUpdate(const DynamicPartitionControlInterface* dynamic_control,
const std::string& slot_suffix) {
// 检查当前分区布局是否为非动态分区
if (!dynamic_control->IsDynamicPartitionsEnabled()) {
// 检查OTA包是否包含动态分区表
if (dynamic_control->GetDynamicPartitionsMetadata() != nullptr) {
LOG(INFO) << "Detected retrofit update from non-dynamic to dynamic";
return true;
}
}
return false;
}
这段代码的逻辑很清晰:如果当前设备不是动态分区,但OTA包里包含了动态分区的元数据,那就说明这是一个retrofit升级。
我的经验:在实际项目中,retrofit的判断条件不止这一个。有些厂商会在OTA包的payload.properties文件中显式声明"retrofit=true"。我个人习惯两种方式都检查,避免漏判。
分区表的转换过程
retrofit最核心的部分,就是分区表的转换。我拿一个实际项目中的例子来说明:
// 转换前的分区表(静态)
Partition 1: system_a (1.2GB)
Partition 2: vendor_a (512MB)
Partition 3: product_a (800MB)
Partition 4: system_b (1.2GB) // 备用槽位
Partition 5: vendor_b (512MB)
Partition 6: product_b (800MB)
// 转换后的分区表(动态)
Partition 1: super_a (2.5GB) // 包含 system, vendor, product
Partition 2: super_b (2.5GB) // 备用槽位
你看,原来6个分区变成了2个super分区。每个super分区内部通过逻辑卷管理来分配空间。这样做的好处很明显——以后调整system、vendor的大小,只需要修改逻辑卷的映射,不用动物理分区表。
避坑指南
我在做retrofit升级时踩过不少坑,挑几个典型的说说:
我曾经遇到过:某款设备在retrofit升级后,重启时卡在bootloader。排查了半天,发现是引导程序没有更新。原来retrofit完成后,bootloader也需要知道新的分区布局,否则它找不到super分区里的system镜像。
解决方案其实不复杂:在retrofit流程的最后一步,必须更新bootloader的配置。具体来说,就是要把分区表信息写入到bootloader可以读取的位置——通常是misc分区或者vbmeta分区。
还有一个坑是空间预留。有些厂商在做retrofit时,没有给super分区预留足够的空间。结果升级到一半,发现空间不够用。我建议至少预留10%的余量,特别是对于重建retrofit模式。
回滚机制
OTA升级最怕的就是失败。retrofit的回滚机制设计得还算巧妙。它利用了Android的A/B槽位机制:
- 升级前:当前槽位(比如slot A)是静态分区,备用槽位(slot B)也是静态分区
- 升级中:将新系统写入备用槽位(slot B),并转换为动态分区
- 升级成功:切换槽位,设备从动态分区启动
- 升级失败:回滚到当前槽位(slot A),仍然是静态分区
这个机制保证了即使升级过程中断电、死机,设备也能正常启动。不过要注意一点:一旦切换槽位成功,原来的静态分区布局就回不去了。所以retrofit确实是一次性操作。
性能影响
有人可能会问:retrofit升级会不会比普通OTA慢?答案是肯定的。因为retrofit多了一步分区表转换和数据迁移的工作。我在项目中实测过,retrofit升级比普通OTA多花大约30%的时间。具体来说:
- 普通OTA:约3-5分钟
- 直接retrofit:约4-7分钟
- 重建retrofit:约6-10分钟
这个时间差异主要取决于数据量大小和存储速度。对于用户来说,多等两三分钟其实可以接受。关键是升级过程要稳定、可靠。
嗯,关于retrofit的核心内容就这些。它不是什么黑科技,但设计得很巧妙。如果你正在做从非动态分区到动态分区的升级方案,希望这些经验能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321