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的关系理清楚:

Product配置与Board配置关系图 Product 配置(产品定义) “做什么” PRODUCT_NAME / PRODUCT_PACKAGES PRODUCT_PROPERTY_OVERRIDES PRODUCT_LOCALES / PRODUCT_COPY_FILES 继承机制:inherit-product Board 配置(设备定义) “在哪跑” TARGET_ARCH / TARGET_CPU_VARIANT BOARD_*_PARTITION_SIZE TARGET_PREBUILT_KERNEL BOARD_FLASH_BLOCK_SIZE 编译时 合并 最终输出:system.img + boot.img + vendor.img

9.7 实战建议

最后,分享几个我在实际项目中总结的经验:

  • 命名规范:Product名和Board名最好保持一致,方便管理。比如产品叫 redfin,Board也叫 redfin
  • 模块化:把公共配置抽成独立的.mk文件,比如 common.mkgms.mkchina.mk,按需继承。
  • 版本管理:Product配置里加上版本号变量,方便追踪。我习惯用 PRODUCT_VERSION_MAJORPRODUCT_VERSION_MINOR
  • 调试技巧:编译时加 showcommands 参数,可以看到所有变量的最终值,排查问题很有用。

一个小技巧:如果你不确定某个变量是否生效,可以在.mk文件里加一行 $(warning VARIABLE_NAME = $($(VARIABLE_NAME))),编译时就会打印出来。这招帮我省了不少排查时间。

好了,Product和Board配置的核心内容就这些。你想想看,其实没那么复杂——Product管软件,Board管硬件,继承机制让复用变得简单。下次你拿到一个新项目,先问自己:这个产品的功能清单是什么?硬件平台是什么?然后分别写到对应的.mk文件里,就对了。

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