9、Product配置与Board配置:产品定义、设备定义、继承机制、产品变量
说实话,刚接触AOSP构建系统那会儿,我最头疼的就是Product和Board这两个概念。它们长得像,职责却完全不同。我见过不少同事把产品配置写在Board里,或者反过来,结果编译出来的镜像要么缺驱动,要么多了一堆不该有的应用。
今天我们就彻底把它理清楚。
9.1 核心概念:Product vs Board
先记住一句话:Product定义“做什么”,Board定义“在哪跑”。
- Product:描述产品的功能特性。比如预装哪些App、系统语言、默认壁纸、是否包含GMS。
- Board:描述硬件平台。比如CPU型号、内存大小、显示分辨率、WiFi芯片、设备树配置。
举个例子:同一款手机硬件(Board),可以出标准版和Pro版(两个Product)。反过来,同一个Product定义,可以适配不同硬件(比如平板和手机共用一套软件功能)。
我个人习惯:把Product想象成“软件清单”,Board想象成“硬件清单”。两者在编译时合并,生成最终的镜像。
9.2 产品定义(Product)
Product配置通常放在 device/<厂商>/<产品名>/ 目录下,核心文件是 产品名.mk。
一个典型的Product配置长这样:
# device/google/redfin/redfin.mk
# 产品名称
PRODUCT_NAME := redfin
PRODUCT_DEVICE := redfin
PRODUCT_BRAND := google
PRODUCT_MODEL := Pixel 5
# 预装应用
PRODUCT_PACKAGES += \
Camera2 \
Gallery2 \
Messaging \
DeskClock
# 系统属性
PRODUCT_PROPERTY_OVERRIDES += \
ro.product.display.resolution=1080x2340 \
ro.build.version.sdk=30
# 语言和区域
PRODUCT_LOCALES := en_US zh_CN ja_JP
# 继承基础配置
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base_telephony.mk)
这里有几个关键变量:
| 变量名 | 作用 | 我在项目中踩过的坑 |
|---|---|---|
PRODUCT_NAME |
产品内部标识,用于区分不同产品 | 命名不要带空格和特殊字符,否则编译脚本会报错 |
PRODUCT_PACKAGES |
指定要编译进镜像的模块(App、库、可执行文件) | 我曾经漏写了某个库,结果运行时一直崩溃 |
PRODUCT_PROPERTY_OVERRIDES |
覆盖系统属性,比如屏幕分辨率、版本号 | 注意属性名不能超过31个字符,这是Android底层限制 |
PRODUCT_LOCALES |
支持的语言列表 | 顺序很重要,第一个是默认语言 |
9.3 设备定义(Board)
Board配置放在 device/<厂商>/<设备名>/ 目录下,核心文件是 BoardConfig.mk。
看个例子:
# device/google/redfin/BoardConfig.mk
# 架构
TARGET_ARCH := arm64
TARGET_ARCH_VARIANT := armv8-a
TARGET_CPU_VARIANT := cortex-a76
# 分区大小(单位:字节)
BOARD_BOOTIMAGE_PARTITION_SIZE := 67108864
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 3221225472
BOARD_USERDATAIMAGE_PARTITION_SIZE := 12884901888
# 内核
TARGET_PREBUILT_KERNEL := device/google/redfin/kernel
BOARD_KERNEL_CMDLINE := console=ttyMSM0,115200n8
# 显示
TARGET_USES_HWC2 := true
BOARD_GPU_DRIVERS := adreno
# 存储
BOARD_FLASH_BLOCK_SIZE := 4096
TARGET_USERIMAGES_USE_EXT4 := true
Board配置的核心变量:
| 变量名 | 作用 | 避坑指南 |
|---|---|---|
TARGET_ARCH |
CPU架构(arm64、arm、x86) | 选错了编译出来的二进制根本跑不起来 |
BOARD_*_PARTITION_SIZE |
各分区大小 | 我曾经把system分区设小了,刷机后一直重启 |
TARGET_PREBUILT_KERNEL |
预编译内核路径 | 路径写错的话,编译不会报错,但刷机后无法开机 |
注意:BoardConfig.mk 中定义的变量,大部分是全局生效的。如果你在Product里定义了同名变量,Board里的会覆盖Product里的。嗯,这个优先级关系要记清楚。
9.4 继承机制
AOSP的继承机制,说白了就是“抄作业”。你可以从一个基础配置继承,然后只修改你需要改的部分。
继承语法有两种:
# 方式一:直接继承
$(call inherit-product, path/to/base.mk)
# 方式二:强制覆盖继承
$(call inherit-product-if-exists, path/to/optional.mk)
举个例子:
# device/mycompany/myproduct/device.mk
# 先继承通用配置
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base_telephony.mk)
# 再继承厂商公共配置
$(call inherit-product, device/mycompany/common/common.mk)
# 最后定义产品特有配置
PRODUCT_PACKAGES += MySpecialApp
PRODUCT_PROPERTY_OVERRIDES += ro.mycompany.feature=true
继承的优先级规则:
- 后继承的配置会覆盖先继承的
- 直接定义的变量优先级最高
- 如果多个继承文件定义了同一个变量,以最后一个为准
我的经验:继承链不要太深,3层以内最好。我曾经维护过一个5层继承的项目,改一个变量要翻半天才知道最终生效的是哪个值。后来我重构了,把公共部分抽成几个独立的.mk文件,按需继承,清爽多了。
9.5 产品变量详解
除了上面提到的,还有一些产品变量你一定会用到:
| 变量名 | 说明 | 使用场景 |
|---|---|---|
PRODUCT_PACKAGES |
要编译的模块列表 | 预装App、系统服务、原生库 |
PRODUCT_COPY_FILES |
要拷贝到镜像中的文件 | 配置文件、固件、预置资源 |
PRODUCT_AAPT_CONFIG |
资源筛选配置 | 指定屏幕密度、语言等 |
PRODUCT_SYSTEM_PROPERTIES |
系统属性(新版推荐) | 替代PRODUCT_PROPERTY_OVERRIDES |
PRODUCT_MANUFACTURER |
制造商名称 | 用于设备识别 |
看一个实际例子:
PRODUCT_PACKAGES += \
MyApp \
libmylib \
my_service
PRODUCT_COPY_FILES += \
device/mycompany/myproduct/wifi.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wifi.conf \
device/mycompany/myproduct/audio_policy.conf:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy.conf
PRODUCT_SYSTEM_PROPERTIES += \
ro.mycompany.version=1.0.0 \
ro.mycompany.buildtype=release
避坑指南:PRODUCT_COPY_FILES 的格式是 源路径:目标路径。目标路径要写完整,我曾经漏写了 $(TARGET_COPY_OUT_VENDOR) 前缀,结果文件被拷贝到了system分区,导致权限问题。
9.6 知识体系总览
下面这张图,帮你把Product和Board的关系理清楚:
9.7 实战建议
最后,分享几个我在实际项目中总结的经验:
- 命名规范:Product名和Board名最好保持一致,方便管理。比如产品叫
redfin,Board也叫redfin。 - 模块化:把公共配置抽成独立的.mk文件,比如
common.mk、gms.mk、china.mk,按需继承。 - 版本管理:Product配置里加上版本号变量,方便追踪。我习惯用
PRODUCT_VERSION_MAJOR和PRODUCT_VERSION_MINOR。 - 调试技巧:编译时加
showcommands参数,可以看到所有变量的最终值,排查问题很有用。
一个小技巧:如果你不确定某个变量是否生效,可以在.mk文件里加一行 $(warning VARIABLE_NAME = $($(VARIABLE_NAME))),编译时就会打印出来。这招帮我省了不少排查时间。
好了,Product和Board配置的核心内容就这些。你想想看,其实没那么复杂——Product管软件,Board管硬件,继承机制让复用变得简单。下次你拿到一个新项目,先问自己:这个产品的功能清单是什么?硬件平台是什么?然后分别写到对应的.mk文件里,就对了。