多分区升级策略:system、vendor、product、odm 分区升级顺序与大小调整
嗯,聊到多分区升级,我得先跟你交个底——这玩意儿在 Android 10 以后越来越复杂了。以前我们做 OTA,基本就盯着 system 分区,顶多再管个 boot。现在呢?vendor、product、odm 全来了,而且它们之间还有依赖关系。说白了,升级顺序搞错了,手机就可能变砖。
我个人习惯把多分区升级比作「搭积木」。你得先搭底座,再往上垒。system 是核心,vendor 是驱动层,product 是厂商定制,odm 是硬件适配。顺序乱了,积木就塌了。
分区升级顺序详解
我在项目中遇到过最典型的场景:某次 OTA 升级后,Wi-Fi 打不开了。查了半天,发现是 vendor 分区里的 Wi-Fi HAL 库没更新,但 system 分区已经升级到了新版本。新 system 调用了新 HAL 接口,老 vendor 根本不认识。这就是升级顺序搞反的典型后果。
正确的升级顺序应该是这样的:
- bootloader 和 radio(如果有)——最底层,先升级
- vendor 分区——硬件抽象层,驱动相关
- odm 分区——OEM 硬件定制,依赖 vendor
- product 分区——厂商应用和服务,依赖 vendor 和 odm
- system 分区——核心系统,依赖所有下层分区
你想想看,为什么 vendor 要排在 system 前面?因为 Android 的 Treble 架构把 vendor 和 system 解耦了。system 里的 framework 通过 HIDL 或 AIDL 调用 vendor 的 HAL。如果 vendor 没升级,system 调用的接口可能就不存在了。
分区大小调整的坑
分区大小调整,说实话,是 OTA 开发里最头疼的问题之一。为什么?因为分区表在 bootloader 里是写死的。你 OTA 脚本再牛,也不能在升级过程中重新分区——那需要擦除整个存储。
我记得有一次,客户要求在新版本里给 product 分区增加 200MB 空间,因为要预装更多应用。但 system 分区已经塞满了,vendor 分区也动不了。怎么办?
常见的解决方案有这几种:
| 方案 | 适用场景 | 风险 |
|---|---|---|
| 动态分区(Dynamic Partition) | Android 10+,支持逻辑分区 | 需要 bootloader 和 kernel 支持 |
| super 分区扩容 | 已有 super 分区,但预留空间不足 | 需要擦除用户数据 |
| 合并分区 | 老旧设备,分区碎片化严重 | 风险极高,容易变砖 |
| AB 分区 + 动态分区 | 新设备设计阶段 | 前期投入大,但最灵活 |
我个人最推荐的是动态分区方案。说白了,就是把 system、vendor、product、odm 都放到一个叫 super 的大分区里,然后在里面动态分配逻辑分区大小。这样你调整分区大小,只需要改分区表描述文件,不需要动物理分区。
实际项目中的分区调整策略
我曾经在一个项目里遇到过这样的场景:产品经理说「我们要在 product 分区里预装 5 个大型游戏,每个 1GB」。但 product 分区原本只有 2GB。system 分区 4GB,vendor 分区 1GB,odm 分区 500MB。
我们当时的做法是:
- 先把 system 分区从 4GB 压缩到 3GB——通过移除一些不常用的系统应用和字体
- 把 product 分区从 2GB 扩展到 4GB——利用 system 省出来的空间
- vendor 和 odm 保持不动——因为它们的大小已经够用
但这里有个关键点:分区调整必须在 OTA 之前完成。我们是通过一次完整的 fastboot 刷机来重新分区,然后再推送增量 OTA 来更新内容。你不能指望 OTA 脚本帮你重新分区,那太危险了。
SVG 流程图:多分区升级顺序与依赖关系
动态分区实战:super 分区管理
如果你用的是 Android 10 以上设备,我强烈建议你上动态分区。它的核心思想是:把所有可变分区都塞进 super 分区,然后在 super 里用逻辑卷管理。
举个例子,你的分区表配置文件(比如 partition_dynamic_layout.xml)可能是这样的:
<!-- 动态分区布局示例 -->
<dynamic_partition>
<super name="super" size="8589934592"> <!-- 8GB -->
<group name="group_basic">
<partition name="system" size="3221225472" /> <!-- 3GB -->
<partition name="vendor" size="1073741824" /> <!-- 1GB -->
<partition name="product" size="2147483648" /> <!-- 2GB -->
<partition name="odm" size="536870912" /> <!-- 512MB -->
</group>
</super>
</dynamic_partition>
你看,所有分区都在 super 里。如果下次你想把 product 从 2GB 扩到 3GB,只需要把 system 从 3GB 缩到 2GB,然后重新生成 OTA 包就行。不需要动物理分区。
升级脚本中的顺序控制
在 updater-script 里,你需要显式控制分区的 mount 和升级顺序。我见过不少新手直接写 apply_patch_check 然后 write_raw_image,完全不考虑顺序。嗯,这样迟早要出事。
正确的做法是:
# 先 mount vendor
mount("ext4", "EMMC", "/dev/block/bootdevice/by-name/vendor", "/vendor", "");
# 升级 vendor
apply_patch_check("/vendor/lib/hw/audio.primary.so", ...);
apply_patch("/vendor/lib/hw/audio.primary.so", ...);
# 再 mount odm
mount("ext4", "EMMC", "/dev/block/bootdevice/by-name/odm", "/odm", "");
# 升级 odm
apply_patch_check("/odm/lib/hw/gralloc.default.so", ...);
apply_patch("/odm/lib/hw/gralloc.default.so", ...);
# 最后 mount system
mount("ext4", "EMMC", "/dev/block/bootdevice/by-name/system", "/system", "");
# 升级 system
apply_patch_check("/system/framework/services.jar", ...);
apply_patch("/system/framework/services.jar", ...);
为什么 vendor 和 odm 要先 mount 再升级?因为 system 里的某些二进制文件在升级过程中可能需要调用 vendor 的库。如果 vendor 还没升级,调用就会失败。
好了,关于多分区升级的策略,核心就是这些。记住一句话:底层先行,上层后动。分区大小调整,能上动态分区就上动态分区,不能上的话,提前规划好预留空间。我在项目里吃过不少亏,希望你能少走这些弯路。