本页将介绍几种机制,Android 设备 OEM 可利用这些机制构建自己的跨产品线共享系统映像 (SSI)。此外,本页还将介绍在基于 AOSP 的通用系统映像 (GSI) 基础上构建 OEM 专有 SSI 的过程。
背景
Treble 计划将整个 Android 拆分为两个部分:特定于硬件的部分(供应商实现)和通用操作系统部分(Android 操作系统框架)。各部分的软件安装于不同分区中:特定于硬件的软件安装于供应商分区中,而通用操作系统软件则安装于系统分区中。一个称为供应商接口 (VINTF) 的版本化接口在这两个分区中定义和强制执行。使用此分区系统,您可以在不修改供应商分区的情况下修改系统分区,反之亦然。
动机
在 AOSP 中发布的框架代码符合 Treble 架构标准,并保持与旧版供应商实现的向后兼容性。例如,基于 Android 10 AOSP 源代码构建的通用系统映像可以在任何搭载 Android 8 或更高版本的符合 Treble 标准的设备上运行。消费者设备上搭载的 Android 版本由 SoC 供应商和 OEM 修改。(请参阅 Android 版本的生命周期。) 对框架做出的这些更改和扩展并未以保持向后兼容性为目标,这增加了操作系统升级的复杂性和成本。此类针对具体设备的变更和修改,增加了升级 Android 操作系统版本的成本和复杂性。
在 Android 11 之前,并无明确架构可帮助合作伙伴为 Android 操作系统框架构建模块化扩展。本文档将介绍 SoC 供应商和 OEM 可执行哪些步骤来创建 SSI。这意味着:在多款设备上重复使用以 Android 操作系统框架源代码为基础构建的同一个映像,从而保持与供应商实现的向后兼容性,并显著降低 Android 操作系统升级的复杂性和成本。如需了解创建 SSI 所需的具体步骤,请参阅基于 GSI 的 SSI 的建议步骤部分。请注意,您不必执行全部 4 个步骤。您选择的步骤(例如,仅第 1 步)取决于您的实现。
SSI 概览
在 SSI 中,特定于产品的软件组件和 OEM 扩展会被放入新的 /product
分区中。/product
分区中的组件使用明确定义的稳定接口与 /system
分区中的组件进行交互。OEM 可以选择构建一个 SSI,也可以选择将少量 SSI 用于多个设备 SKU。Android 操作系统的最新版本发布后,OEM 只需投入一次时间和精力将 SSI 更新至最新 Android 版本。然后即可重复使用 SSI 更新多款设备,而无需更新 /product
分区。
请注意,OEM 和 SoC 供应商构建的 SSI 包含 OEM 所需的所有自定义功能和修改。本页介绍的机制和最佳做法旨在帮助 OEM 实现以下关键目标:
- 在多个设备 SKU 上重复使用 SSI。
- 使用模块化扩展更新 Android 系统,以便更轻松地升级操作系统。
将特定于产品的组件划分到产品分区的核心理念,类似于 Treble 将特定于 SoC 的组件划分到供应商分区的理念。产品接口(类似于 VINTF)可实现 SSI 和产品分区之间的通信。请注意,就 SSI 而言,“组件”这一术语是指安装在映像中的所有资源、二进制文件、文本、库等,而这些本质上会成为分区。
围绕 SSI 的分区
图 1 显示了围绕 SSI 的分区,以及不同分区间的版本化接口和接口政策。本部分将详细介绍每个分区和接口。
图 1. 围绕 SSI 的分区和接口
映像和分区
本部分中的信息对术语“映像”和“分区”进行了区分。
- “映像”是可以独立更新的一组软件的概念性整体。
- “分区”是可以独立更新的一个物理存储位置。
图 1 中各部分的定义如下:
SSI:SSI 是 OEM 通用的映像,可以存在于多个设备上。它不包含任何特定用于硬件或特定于产品的组件。根据定义,指定 SSI 中的所有内容都在使用该 SSI 的所有设备之间共享。SSI 可由单个
/system
映像组成,也可由/system
和/system_ext
分区组成,如图 1 所示。/system
分区包含基于 AOSP 的组件,而/system_ext
实现后则包含 OEM 和 SoC 供应商扩展,以及与 AOSP 组件紧密耦合的组件。例如,为 OEM 自己的应用提供自定义 API 的 OEM Java 框架库,相较/system
分区更适合/system_ext
分区。/system
分区和/system_ext
分区的内容是基于 OEM 修改的 Android 源代码构建而成。/system_ext
分区是可选项,但建议您将其用于与基于 AOSP 的组件紧密耦合的任何自定义功能和扩展。这些区别可帮助您确定需要进行的更改,以便在一段时间内将此类组件从/system_ext
分区迁移到/product
分区。
产品:一组特定于产品或设备的组件,用于表示 OEM 对 Android 操作系统的自定义和扩展。将特定于 SoC 的组件放入
/vendor
分区。SoC 供应商还可以使用/product
分区来支持相应的组件,如独立于 SoC 的组件。例如,如果 SoC 供应商向其 OEM 客户提供了独立于 SoC 的组件(可选择是否随产品一起提供),则 SoC 供应商可以将该组件放于产品映像中。组件的位置并非由其所有权决定,而是由其用途决定。供应商:一组特定于 SoC 的组件。
ODM:并非由 SoC 提供的一组板级组件。通常,SoC 供应商拥有供应商映像,而设备制造商拥有 ODM 映像。若并无单独的
/odm
分区,SoC 供应商和 ODM 映像将合并到/vendor
分区。
映像之间的接口
围绕 SSI 存在两个用于供应商映像和产品映像的主要接口:
供应商接口 (VINTF):VINTF 是位于供应商和 ODM 映像中的组件的接口。产品映像和系统映像中的组件只能通过此接口与供应商和 ODM 映像进行交互。例如,供应商映像不能依赖于系统映像的私有部分,反之亦然。Treble 计划最初提出了该定义,将映像拆分为系统分区和供应商分区。该接口的实现机制如下:
- HIDL(直通式 HAL 仅适用于
system
和system_ext
模块) - 稳定的 AIDL
- 配置
- 系统属性 API
- 配置文件架构 API
- VNDK
- Android SDK API
- Java SDK 库
- HIDL(直通式 HAL 仅适用于
产品接口:产品接口是 SSI 与产品映像之间的接口。定义稳定的接口会将产品组件与 SSI 中的系统组件分离开来。产品接口需要与 VINTF 相同的稳定接口。然而,搭载 Android 11(及更高版本)的设备只会强制执行 VNDK 和 Android SDK API。
在 Android 11 中启用 SSI
本部分将介绍如何在 Android 11 中使用新功能来支持 SSI。
/system_ext 分区
Android 11 中引入了 /system_ext
分区作为可选分区。(该分区是放置 /system
分区中与 AOSP 定义的组件紧密耦合的非 AOSP 组件的位置。)/system_ext
分区被视为是 /system
分区特用于 OEM 的扩展,且未在两个分区间定义接口。/system_ext
分区中的组件可以对 /system
分区进行私有 API 调用,反之,/system
分区中的组件也可以对 /system_ext
分区进行私有 API 调用。
由于两个分区紧密耦合,Android 新版本发布时,这两个分区也会一起升级。为上一个 Android 版本创建的 /system_ext
分区不需要与下一个 Android 版本的 /system
分区兼容。
如需将模块安装到 /system_ext
分区,请将 system_ext_specific:
true
添加到 Android.bp
文件。对于没有 /system_ext
分区的设备,请将此类模块安装到 /system
分区的 ./system_ext
子目录中。
历史信息
以下是一些关于 /system_ext
分区的历史信息。其设计目标是将所有特用于 OEM 的组件放入 /product
分区中,无论这些组件是否通用。然而,无法一次性迁移所有组件,尤其是在某些组件与 /system
分区紧密耦合的情况下。如需将紧密耦合的组件迁移到 /product
分区,必须扩展产品接口。这通常需要对组件本身进行大量重构,需要付出大量的时间和精力。首先将尚未准备好迁移到 /product
分区的组件临时放于 /system_ext
分区。SSI 的目标是最终弃用 /system_ext
分区。
然而,/system_ext
分区有助于使 /system
分区尽可能靠近 AOSP。使用 SSI 时,大部分升级工作都依赖于 /system
分区和 /system_ext
分区中的组件。如果系统映像的构建点与 AOSP 中的源代码相似,您可以将升级工作重点放在 system_ext
映像上。
将 /system 和 /system_ext 分区中的组件取消捆绑,并放入 /product 分区
Android 9 引入了与 /system
分区结合使用的 /product
分区。/product
分区中的模块对系统资源的使用并无任何限制,反之亦然。为了在 Android 10 中实现 SSI,我们将产品组件拆分并放入 /system_ext
分区和 /product
分区。/system_ext
分区不必遵循 /product
分区在 Android 9 中对系统组件的使用限制。从 Android 10 开始,必须将 /product
分区与 /system
分区取消捆绑,并且必须使用来自 /system
分区和 /system_ext
分区的稳定接口。
/system_ext
分区的主要目的是扩展系统功能,而不是 /system_ext partition
部分所述的安装捆绑的产品模块。为此,请将特定于产品的模块取消捆绑,并将其移至 /product
分区中。解绑特定于产品的模块使得 /system_ext
对设备而言变得通用。(如需了解详情,请参阅将 /system_ext 分区设为通用。)
如需将 /product
分区从系统组件中解绑,/product
分区必须具有与 /vendor
分区(已从 Treble 计划解绑)相同的强制执行政策。
从 Android 11 开始,系统会如下所述强制执行 /product
分区的原生接口与 Java 接口。如需了解详情,请参阅强制执行产品分区接口。
- 原生接口:
/product
分区中的原生模块必须与其他分区取消捆绑。产品模块唯一允许的依赖项是来自/system
分区中的部分 VNDK 库(包括 LLNDK)。产品应用所依赖的 JNI 库必须是 NDK 库。 - Java 接口:
/product
分区中的 Java(应用)模块无法使用隐藏 API,因为它们不稳定。这些模块只能使用/system
分区中的公共 API 和系统 API,以及/system
或/system_ext
分区中的 Java SDK 库。您可以为自定义 API 定义 Java SDK 库。
基于 GSI 的 SSI 的建议步骤
图 2. 基于 GSI 的 SSI 的建议分区方式
通用系统映像 (GSI) 是直接基于 AOSP 构建的系统映像。它被用于 Treble 合规性测试(例如,CTS-on-GSI)并作为参考平台,可供应用开发者在没有运行所需 Android 版本的实际设备时,测试应用的兼容性。
OEM 也可以使用 GSI 创建其 SSI。如映像和分区中所述,SSI 由 AOSP 定义的组件的系统映像和 OEM 定义的组件的 system_ext
映像组成。将 GSI 用作 system
映像时,OEM 可以将工作重点放在用于升级的 system_ext
映像上。
本部分为希望在使用 AOSP 或近 AOSP 系统映像时,将其自定义模块化放入 /system_ext
分区和 /product
分区的 OEM 提供指南。如果 OEM 从 AOSP 源代码构建系统映像,则可以使用 AOSP 提供的 GSI 代替他们构建的系统映像。然而,OEM 无需一次性完成全部步骤(按原计划使用 GSI)。
第 1 步:为 OEM 系统映像 (OEM GSI) 沿用 generic_system.mk
通过沿用 generic_system.mk
(在 Android 11 中名为 mainline_system.mk
,并在 AOSP 中重命名为 generic_system.mk
),系统映像 (OEM GSI) 包含 AOSP GSI 中的所有文件。这些文件可由 OEM 修改,因此除 AOSP GSI 文件外,OEM GSI 还可包含特定于 OEM 的文件。然而,不允许 OEM 修改 generic_system.mk
文件本身。
图 3. 为 OEM GSI 沿用 generic_system.mk
第 2 步:使 OEM GSI 与 AOSP GSI 具有相同的文件列表
OEM GSI 无法在此阶段提供额外的文件。特定于 OEM 的文件必须移至 system_ext
分区或 product
分区。
图 4. 将添加的文件移出 OEM GSI
第 3 步:在 OEM GSI 中定义许可名单,以限制修改后的文件
为检查修改后的文件,OEM 可以使用 compare_images
工具,并将 AOSP GSI 与 OEM GSI 进行比较。从 AOSP 启动目标 generic_system_*
获取 AOSP GSI。
通过定期使用 allowlist
参数运行 compare_images
工具,您可以监控允许列表之外的差异。如此,您就无需对 OEM GSI 进行其他修改。
图 5. 定义允许列表,以缩短 OEM GSI 中的已修改文件列表
第 4 步:使 OEM GSI 具有与 AOSP GSI 相同的二进制文件
通过清理允许列表,OEM 可以将 AOSP GSI 用作其产品的系统映像。为清理允许列表,OEM 可以放弃 OEM GSI 中的更改,或将更改提交到 AOSP 中,以便 AOSP GSI 包含更改。
图 6. 使 OEM GSI 具有与 AOSP GSI 相同的二进制文件
为 OEM 定义 SSI
在构建时保护 /system 分区
为避免在 /system
分区中执行任何特定于产品的更改,并定义 OEM GSI,OEM 可以使用名为 require-artifacts-in-path
的 makefile 宏,来阻止在调用该宏后进行任何系统模块声明。请参阅创建 makefile 和启用工件路径检查示例。
OEM 可以定义一个列表,以允许在 /system
分区中临时安装特定于产品的模块。然而,该列表必须为空,以使 OEM GSI 对所有 OEM 产品通用。此过程用于定义 OEM GSI,并且可独立于 AOSP GSI 的步骤。
强制执行产品接口
为保证 /product
分区取消捆绑,OEM 可以通过为原生模块设置 PRODUCT_PRODUCT_VNDK_VERSION:= current
以及为 Java 模块设置 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE:= true
,确保其设备强制执行产品接口。如果设备的 PRODUCT_SHIPPING_API_LEVEL
大于或等于 30
,系统会自动设置这些变量。如需了解详情,请参阅强制执行产品分区接口。
将 /system_ext 分区设为通用
/system_ext
分区可能因设备而异,因为它可以具有特定于设备的系统捆绑模块。由于 SSI 由 /system
和 /system_ext
分区组成,因此 /system_ext
分区的差异会阻碍 OEM 定义 SSI。OEM 可以拥有自己的 SSI,并可以通过消除任何差异并将 /system_ext
分区设为通用,而在多个设备之间共享该 SSI。
本部分介绍有关将 /system_ext
分区设为通用的建议。
在系统分区中公开隐藏 API
许多特定于产品的应用无法安装在产品分区中,因为它们使用隐藏 API,产品分区中禁止使用此类 API。如需将特定于设备的应用迁移到产品分区,请撤消使用隐藏的 API。
从应用中移除隐藏 API 的首选方法是用公共 API 或系统 API 进行替换。如并无可用于替换的 API,OEM 可以帮助 AOSP 为其设备定义新的系统 API。
此外,OEM 也可以通过在 /system_ext
分区中创建自己的 Java SDK 库来定义自定义 API。它可以使用系统分区中的隐藏 API,也可以向产品分区或供应商分区中的应用提供 API。OEM 必须冻结面向产品的 API 以实现向后兼容性。
包含所有 APK 超集,并跳过每台设备的部分软件包安装
与系统捆绑的某些软件包在不同设备之间并不通用。很难将这些 APK 模块取消捆绑,并将其迁移到产品分区或供应商分区中。作为临时解决方案,OEM 可以让 SSI 包含所有模块,然后通过 SKU 属性 (ro.boot.hardware.sku
) 过滤不需要的模块。为使用过滤器,OEM 可叠加框架资源 config_disableApkUnlessMatchedSku_skus_list
和 config_disableApksUnlessMatchedSku_apk_list
。
如需进行更精确的设置,请声明用于停用不必要的软件包的广播接收器。广播接收器在收到 ACTION_BOOT_COMPLETED
消息时会调用 setApplicationEnabledSetting
以停用该软件包。
定义 RRO,而不使用静态资源叠加层
静态资源叠加层会处理叠加软件包。然而,它可能会阻碍定义 SSI,因此请确保已开启并正确设置 RRO 的属性。通过如下方式设置属性,OEM 可以将所有自动生成的叠加层设置为 RRO。
PRODUCT_ENFORCE_RRO_TARGETS := *
PRODUCT_ENFORCE_RRO_EXCLUDED_OVERLAYS := # leave it empty
如需详细配置,请手动定义 RRO,而不是依靠自动生成的配置。如需了解详情,请参阅运行时资源叠加层 (RRO)。
此外,OEM 还可以使用 android:requiredSystemPropertyName
和 android:requiredSystemPropertyValue
属性定义依赖于系统属性的条件 RRO。
常见问题解答 (FAQ)
我能否定义多个 SSI?
具体取决于设备(或设备组)的共性和特性。OEM 可以尝试将 system_ext
分区设为通用,如将 system_ext 分区设为通用中所述。如果设备组具有许多差异,最好定义多个 SSI。
我能否修改 OEM GSI 的 generic_system.mk (mainline_system.mk)?
不能。但 OEM 可为沿用 generic_system.mk
文件的 OEM GSI 定义新 makefile,并改用新的 makefile。有关示例,请参阅强制执行产品分区接口。
我能否从 generic_system.mk 移除与实现相冲突的模块?
不能。GSI 有一组最低限度的可启动和可测试的模块集。如果您认为某个模块并非必需,请通过提交 bug 来更新 AOSP 中的 generic_system.mk
文件。