20、构建系统:Android.bp 与 Soong,产品配置(Product Config),车载专属模块编译
各位好,我是你们的老朋友。今天我们来聊聊 Android Automotive OS 的构建系统。说实话,我刚接触 Android 构建时,也被 Makefile 和 Android.mk 搞得晕头转向。后来 Google 推出了 Soong 和 Android.bp,我才觉得这事儿终于靠谱了。
这一章,我会带你搞懂三件事:Android.bp 怎么写、产品配置怎么玩、以及车载模块怎么编译。嗯,都是实战中天天要用的东西。
20.1 从 Android.mk 到 Android.bp:为什么变?
老项目里,你看到的都是 Android.mk 文件。它基于 GNU Make,语法灵活但慢得要命。我印象特别深,有一次编译整个系统,光解析 Makefile 就花了快十分钟。
Soong 就是来救场的。它用 Ninja 做底层构建工具,解析速度提升了一个数量级。而 Android.bp 是 Soong 的配置文件,语法类似 JSON,简洁明了。
核心区别一句话:Android.mk 是过程式(告诉系统怎么做),Android.bp 是声明式(告诉系统要什么)。
举个例子,你写一个原生库,Android.mk 要写一堆 LOCAL_ 变量:
# Android.mk 老写法
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libmyfeature
LOCAL_SRC_FILES := myfeature.cpp
LOCAL_SHARED_LIBRARIES := libbase liblog
include $(BUILD_SHARED_LIBRARY)
换成 Android.bp,清爽多了:
// Android.bp 新写法
cc_library_shared {
name: "libmyfeature",
srcs: ["myfeature.cpp"],
shared_libs: ["libbase", "liblog"],
}
我个人习惯,新项目一律用 Android.bp。除非你要维护老代码,否则别碰 Android.mk。
20.2 Android.bp 核心语法速览
Android.bp 的模块类型很多,但车载开发常用的就那么几种。我帮你整理了一下:
| 模块类型 | 用途 | 常见场景 |
|---|---|---|
cc_library_shared |
编译动态库 (.so) | 车载 HAL 实现、服务库 |
cc_binary |
编译可执行文件 | 车载守护进程、测试工具 |
java_library |
编译 Java 库 (jar) | 车载系统服务、API 接口 |
android_app |
编译 APK | 车载 Launcher、设置应用 |
aidl_interface |
编译 AIDL 接口 | 车载 HIDL/AIDL 服务定义 |
写 Android.bp 时,有几个字段特别容易踩坑:
- name:模块名,全局唯一。我建议用项目前缀,比如
automotive_,避免冲突。 - srcs:源文件列表。支持通配符,但别滥用,否则增量编译会变慢。
- shared_libs:运行时依赖的动态库。注意,这里只写库名,不用写
lib前缀和.so后缀。 - static_libs:编译时静态链接的库。车载模块里,我建议尽量用动态库,节省镜像空间。
小技巧:如果你不确定某个字段怎么用,直接去源码里搜 Android.bp 文件,看看 Google 官方模块怎么写。比如 packages/services/Car/ 目录下就有很多车载专属的 bp 文件。
20.3 产品配置:Product Config 到底配什么?
产品配置,说白了就是告诉构建系统:我这台车要装哪些模块?
Android 的产品配置放在 device/ 目录下,每个 OEM 有自己的文件夹。比如:
device/
└── mycompany/
└── mycar/
├── AndroidProducts.mk
├── device.mk
├── BoardConfig.mk
└── product.mk
这里最关键的是 product.mk。它定义了产品要包含哪些模块。举个例子:
# product.mk
PRODUCT_PACKAGES += \
CarLauncher \
CarSettings \
CarService \
libcarframework \
automotive_can_hal \
automotive_cluster_service
你看,车载专属模块都在这里列出来。如果你要加一个新模块,比如车载语音助手,就在 PRODUCT_PACKAGES 里加上模块名。
我曾经犯过一个错:在 product.mk 里加了一个模块,但忘记在 Android.bp 里定义 name。结果编译报错说找不到模块。嗯,从那以后我每次加模块都会先确认 bp 文件有没有写对。
20.4 车载专属模块编译实战
车载系统里,有些模块是手机没有的。比如 CAN 总线 HAL、仪表盘服务、车辆属性服务。这些模块的编译,有一些特殊之处。
20.4.1 车载 HAL 模块
车载 HAL 通常用 HIDL 或 AIDL 定义接口。以 CAN HAL 为例:
// hardware/interfaces/automotive/can/1.0/Android.bp
hidl_interface {
name: "android.hardware.automotive.can@1.0",
srcs: ["types.hal", "ICanBus.hal", "ICanController.hal"],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
gen_java_constants: true,
}
注意 gen_java: true,这个会生成 Java 接口,供上层服务调用。如果你只做 C++ 实现,可以关掉它,加快编译速度。
20.4.2 车载系统服务
车载系统服务通常用 Java 写,编译成 APK 或 jar 包。比如 CarService:
// packages/services/Car/Android.bp
java_library {
name: "car-service",
srcs: [
"src/**/*.java",
],
libs: [
"car-framework",
"android.car",
],
static_libs: [
"can-utils",
],
installable: true,
}
这里 installable: true 表示这个库会安装到系统镜像里。如果你只是编译测试用,可以设成 false。
20.4.3 车载专属编译选项
车载模块有时需要特殊的编译标志。比如,有些芯片不支持浮点运算,你得用软浮点:
cc_library_shared {
name: "libmycarfeature",
srcs: ["mycarfeature.cpp"],
cflags: [
"-msoft-float",
"-O2",
],
target: {
android_arm: {
cflags: ["-march=armv7-a"],
},
android_arm64: {
cflags: ["-march=armv8-a"],
},
},
}
你看,target 字段可以针对不同架构设置不同参数。这在车载多平台适配时特别有用。
注意:车载模块的编译选项,一定要和硬件团队确认。我曾经因为 GCC 优化等级设太高,导致 CAN 驱动在特定芯片上跑飞。后来改成 -O1 才稳定。
20.5 知识体系总览
为了让你更直观地理解,我画了一张图,把本章的核心逻辑串起来:
这张图展示了构建系统的完整流程。左边是输入(Android.bp、Product Config、Board Config),中间是 Soong 引擎,右边是输出(车载模块和系统镜像)。
20.6 避坑指南与个人经验
最后,分享几个我踩过的坑,希望能帮你省点时间:
- 模块名冲突:Android 源码里模块名是全局唯一的。如果你不小心和系统模块重名,编译会报错。我习惯在车载模块名前加
automotive_前缀。 - 依赖缺失:在 product.mk 里加了模块,但模块依赖的库没加。编译时不会报错,但运行时就会 crash。建议用
m -j32全量编译一次,确保所有依赖都满足。 - 缓存问题:修改 Android.bp 后,有时增量编译不生效。我一般先
rm -rf out/再重新编译,虽然慢点但保险。 - 车载专属模块的权限:有些车载模块需要 system 权限,记得在 Android.bp 里加
privileged: true,否则安装时会报错。
我的建议:刚开始接触车载构建系统时,别急着写复杂的 bp 文件。先找一个现成的车载模块,比如 CarLauncher,看看它的 Android.bp 怎么写,然后照着改。这样上手最快。
好了,这一章的内容就到这里。构建系统是 Android 开发的基石,搞懂了它,你就能随心所欲地往系统里加车载功能了。