多分区升级策略:system、vendor、product、odm 分区升级顺序与大小调整

嗯,聊到多分区升级,我得先跟你交个底——这玩意儿在 Android 10 以后越来越复杂了。以前我们做 OTA,基本就盯着 system 分区,顶多再管个 boot。现在呢?vendor、product、odm 全来了,而且它们之间还有依赖关系。说白了,升级顺序搞错了,手机就可能变砖。

我个人习惯把多分区升级比作「搭积木」。你得先搭底座,再往上垒。system 是核心,vendor 是驱动层,product 是厂商定制,odm 是硬件适配。顺序乱了,积木就塌了。

核心原则:分区升级顺序必须遵循依赖关系——先升级底层分区,再升级上层分区。vendor 和 odm 必须在 system 之前升级,因为 system 依赖它们提供的 HAL 和驱动接口。

分区升级顺序详解

我在项目中遇到过最典型的场景:某次 OTA 升级后,Wi-Fi 打不开了。查了半天,发现是 vendor 分区里的 Wi-Fi HAL 库没更新,但 system 分区已经升级到了新版本。新 system 调用了新 HAL 接口,老 vendor 根本不认识。这就是升级顺序搞反的典型后果。

正确的升级顺序应该是这样的:

  1. bootloader 和 radio(如果有)——最底层,先升级
  2. vendor 分区——硬件抽象层,驱动相关
  3. odm 分区——OEM 硬件定制,依赖 vendor
  4. product 分区——厂商应用和服务,依赖 vendor 和 odm
  5. system 分区——核心系统,依赖所有下层分区

你想想看,为什么 vendor 要排在 system 前面?因为 Android 的 Treble 架构把 vendor 和 system 解耦了。system 里的 framework 通过 HIDL 或 AIDL 调用 vendor 的 HAL。如果 vendor 没升级,system 调用的接口可能就不存在了。

我的经验:在编写 OTA 脚本时,我会在 updater-script 里显式指定分区的 mount 顺序和升级顺序。不要依赖默认行为,因为不同厂商的 recovery 实现可能不一样。

分区大小调整的坑

分区大小调整,说实话,是 OTA 开发里最头疼的问题之一。为什么?因为分区表在 bootloader 里是写死的。你 OTA 脚本再牛,也不能在升级过程中重新分区——那需要擦除整个存储。

我记得有一次,客户要求在新版本里给 product 分区增加 200MB 空间,因为要预装更多应用。但 system 分区已经塞满了,vendor 分区也动不了。怎么办?

常见的解决方案有这几种:

方案 适用场景 风险
动态分区(Dynamic Partition) Android 10+,支持逻辑分区 需要 bootloader 和 kernel 支持
super 分区扩容 已有 super 分区,但预留空间不足 需要擦除用户数据
合并分区 老旧设备,分区碎片化严重 风险极高,容易变砖
AB 分区 + 动态分区 新设备设计阶段 前期投入大,但最灵活

我个人最推荐的是动态分区方案。说白了,就是把 system、vendor、product、odm 都放到一个叫 super 的大分区里,然后在里面动态分配逻辑分区大小。这样你调整分区大小,只需要改分区表描述文件,不需要动物理分区。

注意:动态分区虽然灵活,但要求 bootloader 和 kernel 都支持。如果你的设备是 Android 9 及以下,就别想了。另外,从非动态分区切换到动态分区,通常需要一次完整的擦除升级(clean flash),不能直接 OTA。

实际项目中的分区调整策略

我曾经在一个项目里遇到过这样的场景:产品经理说「我们要在 product 分区里预装 5 个大型游戏,每个 1GB」。但 product 分区原本只有 2GB。system 分区 4GB,vendor 分区 1GB,odm 分区 500MB。

我们当时的做法是:

  1. 先把 system 分区从 4GB 压缩到 3GB——通过移除一些不常用的系统应用和字体
  2. 把 product 分区从 2GB 扩展到 4GB——利用 system 省出来的空间
  3. vendor 和 odm 保持不动——因为它们的大小已经够用

但这里有个关键点:分区调整必须在 OTA 之前完成。我们是通过一次完整的 fastboot 刷机来重新分区,然后再推送增量 OTA 来更新内容。你不能指望 OTA 脚本帮你重新分区,那太危险了。

避坑指南:我曾经在调整分区大小时,忘记更新 bootloader 里的分区表。结果升级后,kernel 按照旧分区表去挂载分区,直接挂载失败,系统起不来。从那以后,我每次调整分区大小,都会再三确认 bootloader、kernel cmdline、recovery 三处的分区表是否一致。

SVG 流程图:多分区升级顺序与依赖关系

多分区升级顺序与依赖关系 bootloader / radio vendor 分区 odm 分区 product 分区 system 分区 最底层,先升级 硬件抽象层,驱动相关 OEM 硬件定制 厂商应用和服务 核心系统,最后升级 依赖关系: system 依赖 vendor/odm/product product 依赖 vendor/odm odm 依赖 vendor

动态分区实战: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 包就行。不需要动物理分区。

小技巧:在动态分区里,我习惯给每个 group 预留 10%-20% 的未分配空间。这样以后调整大小,不用动其他分区,直接用预留空间就行。比如 8GB 的 super,我只分配 6.5GB,留 1.5GB 做缓冲。

升级脚本中的顺序控制

在 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 还没升级,调用就会失败。

特别提醒:在 AB 分区系统里,升级顺序同样重要。虽然 AB 分区可以后台升级,但如果你在升级 system 时,vendor 还是旧版本,重启后 system 调用 vendor 接口就可能出问题。所以 AB 分区也要保证升级顺序一致。

好了,关于多分区升级的策略,核心就是这些。记住一句话:底层先行,上层后动。分区大小调整,能上动态分区就上动态分区,不能上的话,提前规划好预留空间。我在项目里吃过不少亏,希望你能少走这些弯路。

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