23、构建系统与定制:Android.bp 与 Soong、产品配置、分区定制
说到 Android Automotive 的构建系统,很多人第一反应就是「不就是 make 一下吗?」。嗯,如果你真这么想,那在车机项目里大概率会踩坑。我最早接触 Android 构建时,还是 Android.mk 的时代,那时候改个模块依赖都能折腾半天。后来 Google 推出了 Soong 和 Android.bp,说实话,刚开始我是拒绝的——又得学一套新语法。但用了一段时间后,我不得不承认:真香。
今天我们就来聊聊 Automotive 构建系统的核心。说白了,就是三件事:怎么写构建文件、怎么配产品、怎么管分区。
23.1 Android.bp 与 Soong:新一代构建核心
Soong 是 Android 从 Nougat 开始引入的构建系统,用来替代老旧的 Makefile。它的输入文件是 Android.bp,采用类似 JSON 的声明式语法。你可能会问:为什么要换?
我个人的理解是:Makefile 太灵活了,灵活到容易写出「幽灵依赖」。而 Android.bp 强制你声明清楚每个模块的源文件、依赖、编译选项,构建系统可以更好地做增量编译和并行优化。
23.1.1 基本语法
来看一个最简单的例子。假设我们要编译一个原生服务:
cc_binary {
name: "my_car_service",
srcs: ["main.cpp", "service.cpp"],
shared_libs: [
"libbinder",
"libutils",
"libhardware",
],
cflags: ["-Wall", "-Werror"],
init_rc: "my_car_service.rc",
}
这里 cc_binary 是模块类型,表示编译一个 C++ 可执行文件。类似的还有 cc_library、java_library、android_app 等等。每个模块都有一个唯一的 name,其他模块可以通过这个名字来引用它。
packages/services/Car/service/,模块名就叫 car_service。团队里其他人一看就懂。
23.1.2 模块依赖与可见性
Android 11 之后,Soong 引入了 visibility 机制。说白了,就是控制「谁能用我这个模块」。以前在 Makefile 里,任何模块都可以随意链接你的库,很容易造成循环依赖或者接口滥用。
cc_library {
name: "libcar_common",
srcs: ["common.cpp"],
visibility: [
"//packages/services/Car:__subpackages__",
"//vendor/xxx:__subpackages__",
],
}
上面这个配置表示:只有 packages/services/Car 和 vendor/xxx 目录下的模块才能依赖 libcar_common。其他模块想用?对不起,编译直接报错。
23.1.3 Soong 的扩展机制
Soong 还支持用 Go 语言写扩展,叫做 soong_build 插件。不过说实话,大部分场景用不到。如果你只是做车机定制,掌握 Android.bp 的基本语法就够用了。只有当你需要自定义编译规则(比如生成代码、处理特殊资源)时,才需要碰 Go 扩展。
23.2 产品配置:从 AOSP 到你的车机
产品配置,说白了就是告诉构建系统:你要编译一个什么样的系统?是给哪款车用的?有哪些硬件?预装哪些应用?
在 AOSP 里,产品配置通常放在 device/ 或 vendor/ 目录下。一个典型的产品配置包含以下几个文件:
- BoardConfig.mk:定义硬件相关参数,比如芯片架构、分区大小、内核路径。
- device.mk:定义产品相关的模块列表,比如要编译哪些包、预装哪些 APK。
- product.mk:定义产品名称、品牌、设备代号等。
- AndroidProducts.mk:把上面的配置组合起来,形成一个完整的产品。
举个例子,假设我们要为某款车机定义产品:
# device/mycompany/my_car/device.mk
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
$(call inherit-product, packages/services/Car/car_product/build/car.mk)
PRODUCT_NAME := my_car
PRODUCT_DEVICE := my_car
PRODUCT_BRAND := MyCompany
PRODUCT_MODEL := MyCarHeadUnit
# 预装车机应用
PRODUCT_PACKAGES += \
MyCarLauncher \
MyCarSettings \
MyCarMedia
# 添加车机特有权限
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.type.automotive.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.type.automotive.xml
这里的关键是 inherit-product,它允许你继承其他产品的配置。比如 car.mk 是 Google 提供的 Automotive 基础配置,里面包含了车机需要的系统服务、权限、默认应用等。你只需要在此基础上添加自己的东西就行。
23.3 分区定制:system、vendor、product 与 odm
分区是 Android 构建里最容易让人迷糊的地方。尤其是 Automotive,分区设计比手机更复杂。我们先理清几个关键分区的作用:
| 分区 | 用途 | 谁可以修改 |
|---|---|---|
system |
Android 框架、系统应用、核心库 | SoC 厂商 / Google |
vendor |
硬件抽象层(HAL)、驱动、厂商特有服务 | 硬件厂商 |
product |
产品定制内容,如预装应用、壁纸、配置 | OEM / 车厂 |
odm |
原始设计制造商定制,如传感器校准数据 | ODM |
为什么要分这么多分区?说白了,是为了解耦。Google 只更新 system,硬件厂商只更新 vendor,车厂只更新 product。大家互不干扰,OTA 升级时也可以只推送自己负责的分区。
23.3.1 分区定制实战
假设我们要在 product 分区里添加一个车机专属的配置文件。首先,在 device/mycompany/my_car/ 下创建目录结构:
device/mycompany/my_car/
├── product/
│ └── etc/
│ └── car_config.xml
└── device.mk
然后在 device.mk 里添加:
PRODUCT_COPY_FILES += \
device/mycompany/my_car/product/etc/car_config.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/car_config.xml
这样编译后,car_config.xml 就会出现在 /product/etc/ 目录下。应用可以通过 getprop 或者直接读取文件来获取配置。
system 分区,结果 OTA 升级时被覆盖了。后来才意识到,车厂定制的数据应该放在 product 或 data 分区,而不是 system。记住:system 是 Google 的地盘,别乱动。
23.3.2 动态分区与超级镜像
Android 10 之后引入了动态分区。什么意思呢?以前每个分区的大小是固定的,比如 system 分 2GB,vendor 分 1GB。如果 system 不够用,vendor 却有大量空闲,那就浪费了。
动态分区允许所有分区共享一个「超级镜像」(super.img),空间按需分配。在 Automotive 上,这个特性特别有用——因为车机的硬件配置五花八门,分区大小很难提前定死。
配置动态分区需要在 BoardConfig.mk 里设置:
TARGET_USERIMAGES_USE_EXT4 := true
BOARD_SUPER_PARTITION_SIZE := 4294967296 # 4GB
BOARD_SUPER_PARTITION_GROUPS := my_group
BOARD_MY_GROUP_SIZE := 4294967296
BOARD_MY_GROUP_PARTITION_LIST := system vendor product odm
这样,system、vendor、product、odm 四个分区都放在一个 4GB 的超级分区里,空间自动调节。
23.4 知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考:
23.5 一些实战建议
最后,分享几个我在 Automotive 项目里积累的经验:
- 善用
soong_log:编译时加SOONG_LOG=debug可以打印 Soong 的解析过程,排查依赖问题特别好用。 - 模块命名要规范:我见过有人把车机服务命名为
car_service,也有人叫my_car_service,还有人叫service_car。同一个项目里三种命名风格,找模块时简直崩溃。建议团队统一前缀,比如automotive_开头。 - 分区大小别算太死:车机的存储芯片型号可能会变,留 20% 的余量比较安全。我吃过亏,第一次流片时
product分区只留了 100MB,结果客户要加 3 个预装应用,直接爆了。 - 用好
PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE:这个开关可以强制所有产品定制内容只能放在product分区,防止不小心污染system或vendor。建议默认开启。
嗯,构建系统这块内容确实有点干,但它是车机定制的基石。你想想看,如果连模块怎么编译、文件放哪个分区都没搞清楚,后面做功能定制时一定会手忙脚乱。希望今天的分享能帮你理清思路。
公众号:蓝海资料掘金营,微信deep3321