Android 11 取消了 product
分区捆绑,使其独立于 system
和 vendor
分区。作为这些变更的一部分,您现在可以控制 product
分区对原生和 Java 接口的访问权限(与 vendor
分区的接口强制工作原理类似)。
强制执行原生接口
如需启用原生接口强制执行,请将 PRODUCT_PRODUCT_VNDK_VERSION
设置为 current
。(当目标的 Shipping API 级别大于 29 时,版本会自动设置为 current
。)强制执行允许:
product
分区中的原生模块:- 以静态或动态方式链接到
product
分区(包含静态、共享或头文件库)中的其他模块。 - 动态链接到
system
分区中的 VNDK 库。
- 以静态或动态方式链接到
product
分区中未捆绑 APK 的 JNI 库链接到/product/lib
或/product/lib64
中的库(这是对 NDK 库的补充)。
强制执行不允许链接到 product
分区以外的其他分区。
构建时强制执行 (Android.bp)
在 Android 11 中,除核心和供应商映像变体外,系统模块还可以创建产品映像变体。启用原生接口强制执行后(PRODUCT_PRODUCT_VNDK_VERSION
设置为 current
):
product
分区中的原生模块位于产品变体中,而不是核心变体中。在其
Android.bp
文件中包含vendor_available: true
的模块可用于产品变体和供应商变体。指定
product_specific: true
的库或二进制文件可以链接到在其Android.bp
文件中指定product_specific: true
或vendor_available: true
的其他库。VNDK 库的
Android.bp
文件中必须包含vendor_available: true
,以便product
二进制文件可以链接到 VNDK 库。
下表总结了用于创建映像变体的 Android.bp
属性。
Android.bp 中的属性 | 已创建的变体 | |
---|---|---|
强制执行前 | 强制执行后 | |
默认(无) | 核心
(包含 |
核心
(包含 |
system_ext_specific: true |
核心 | 核心 |
product_specific: true |
核心 | 产品 |
vendor: true |
供应商 | 供应商 |
vendor_available: true |
核心、供应商 | 核心、产品、供应商 |
system_ext_specific: true 和 vendor_available:
true |
核心、供应商 | 核心、产品、供应商 |
product_specific: true 和 vendor_available:
true |
核心、供应商 | 产品、供应商 |
构建时强制执行 (Android.mk)
启用原生接口强制执行后,安装到 product
分区的原生模块将具有一个 native:product
链接类型,该链接类型只能链接到其他 native:product
或 native:vndk
模块。尝试链接到除上述类型之外的任何模块会导致构建系统生成链接类型检查错误。
运行时强制执行
启用原生接口强制执行后,Bionic 链接器的链接器配置不允许系统进程使用 product
库,这样会为 product
进程创建 product
部分,此类进程无法链接到 product
分区以外的库(但是,此类进程可以链接到 VNDK 库)。尝试违反运行时链接配置会导致进程失败并生成 CANNOT LINK EXECUTABLE
错误消息。
强制执行 Java 接口
如需启用 Java 接口强制执行,请将 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE
设置为 true
。(当目标的 Shipping API 级别大于 29 时,该值会自动设置为 true
。)启用后,强制执行将允许/禁止以下访问权限。
API | /system | /system_ext | /product | /vendor | /data |
---|---|---|---|---|---|
公共 API | |||||
@SystemApi | |||||
@hide API |
与 vendor
分区一样,product
分区中的应用或 Java 库只能使用公共和系统 API;不允许链接到使用隐藏 API 的库。此限制适用于构建时的链接以及运行时的反射。
构建时强制执行
构建时,Make 和 Soong 将通过检查 platform_apis
和 sdk_version
字段来验证 product
分区中的 Java 模块是否不使用隐藏 API。product
分区中应用的 sdk_version
必须填写 current
、system_current
或 API 的数字版本,platform_apis
字段必须为为空。
运行时强制执行
Android 运行时会验证 product
分区中的应用是否不使用隐藏 API,包括反射。如需了解详情,请参阅针对非 SDK 接口的限制。
启用产品接口强制执行
按照本部分中的步骤启用产品接口强制执行。
步骤 | 任务 | 必需 |
---|---|---|
1 | 定义您自己的系统 Makefile,指定 system 分区的软件包,然后在 device.mk 中设置工件路径要求检查(防止非系统模块安装到 system 分区)。 |
否 |
2 | 清理允许列表。 | 否 |
3 | 强制执行原生接口并确定运行时链接失败(可以与 Java 强制执行并行运行)。 | 是 |
4 | 强制执行 Java 接口并验证运行时行为(可以与原生强制执行并行运行)。 | 是 |
5 | 检查运行时行为。 | 是 |
6 | 通过产品接口强制执行更新 device.mk 。 |
是 |
第 1 步:创建 Makefile 并启用工件路径检查
在该步骤中定义 system
Makefile。
创建一个 Makefile,定义
system
分区的软件包。例如,使用以下命令创建一个oem_system.mk
文件:$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system.mk) $(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system.mk) # Applications PRODUCT_PACKAGES += \ CommonSystemApp1 \ CommonSystemApp2 \ CommonSystemApp3 \ # Binaries PRODUCT_PACKAGES += \ CommonSystemBin1 \ CommonSystemBin2 \ CommonSystemBin3 \ # Libraries PRODUCT_PACKAGES += \ CommonSystemLib1 \ CommonSystemLib2 \ CommonSystemLib3 \ PRODUCT_SYSTEM_NAME := oem_system PRODUCT_SYSTEM_BRAND := Android PRODUCT_SYSTEM_MANUFACTURER := Android PRODUCT_SYSTEM_MODEL := oem_system PRODUCT_SYSTEM_DEVICE := generic # For system-as-root devices, system.img should be mounted at /, so we # include ROOT here. _my_paths := \ $(TARGET_COPY_OUT_ROOT)/ \ $(TARGET_COPY_OUT_SYSTEM)/ \ $(call require-artifacts-in-path, $(_my_paths),)
在
device.mk
文件中,继承system
分区的通用 Makefile,并启用工件路径要求检查。例如:$(call inherit-product, $(SRC_TARGET_DIR)/product/oem_system.mk) # Enable artifact path requirements checking PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := strict
工件路径要求简介
将 PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS
设置为 true
或 strict
时,构建系统会阻止其他 Makefile 中定义的软件包安装到 require-artifacts-in-path
中定义的路径,并阻止当前 Makefile 中定义的软件包将工件安装到 require-artifacts-in-path
中定义的路径之外。
在上例中,将 PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS
设置为 strict
后,oem_system.mk
之外的 Makefile 不能包含安装到 root
或 system
分区的模块。如需包含这些模块,您必须在 oem_system.mk
文件或所含的 Makefile 中定义它们。尝试将模块安装到禁止路径会导致构建中断。如需解决中断问题,请执行以下操作之一:
选项 1:在
oem_system.mk
中包含的 Makefile 中包含系统模块。这样一来,便可以满足工件路径要求(因为模块现在包含在所含的 Makefile 中),从而允许安装到“require-artifacts-in-path”中的路径集。选项 2:将模块安装到
system_ext
或product
分区(不将模块安装到system
分区)。选项 3:将模块添加到
PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST
。这样会列出允许安装的模块。
第 2 步:清空允许列表
在此步骤中,请清空 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST
,以便共享 oem_system.mk
的所有设备也可以共享一个 system
映像。如需清空允许列表,请将列表中的所有模块移动到 system_ext
或 product
分区,或将其添加到 system
Make 文件。此步骤是可选的,因为启用产品接口强制执行无需定义通用的 system
映像。但是,清空允许列表有助于使用 system_ext
定义 system
边界。
第 3 步:强制执行原生接口
在此步骤中,您将设置 PRODUCT_PRODUCT_VNDK_VERSION := current
,然后查找构建和运行时错误并予以解决。如需检查设备启动和日志并查找和修复运行时链接失败,请执行以下操作:
设置
PRODUCT_PRODUCT_VNDK_VERSION := current
。构建设备并查找构建错误。您可能会看到缺少产品变体或核心变体的一些构建中断。常见的中断包括:
- 任何具有
product_specific: true
的hidl_interface
模块都将不适用于系统模块。如需修复此错误,请将product_specific: true
替换为system_ext_specfic: true
。 - 模块可能缺少产品模块所需的产品变体。如需修复此错误,请通过设置
vendor_available: true
将该模块提供给product
分区,或通过设置product_specific: true
将模块移动到product
分区。
- 任何具有
解决构建错误并确保设备构建成功。
刷写映像并在设备启动和日志中查找运行时错误。
- 如果测试用例日志中的
linker
标签显示CANNOT LINK EXECUTABLE
消息,那么 Make 文件缺少依赖项(并且在构建时不会被捕获)。 - 如需从构建系统进行检查,请将所需的库添加到
shared_libs:
或required:
字段。
- 如果测试用例日志中的
按照上述指南解析缺少的依赖项。
第 4 步:强制执行 Java 接口
在此步骤中,您将设置 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true
,然后查找并修复导致的构建错误。查找以下两种类型的错误:
链接类型错误。此错误表示应用链接到具有更广泛的
sdk_version
的 Java 模块。如需解决此错误,您可以扩展应用的sdk_version
或限制库的sdk_version
。示例错误:error: frameworks/base/packages/SystemUI/Android.bp:138:1: module "SystemUI" variant "android_common": compiles against system API, but dependency "telephony-common" is compiling against private API.Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.
符号错误。此错误表示某个符号位于隐藏 API 中,因此无法找到。如需修复此错误,请使用可见(非隐藏)API 或查找替代项。示例错误:
frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java:1051: error: cannot find symbol ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( ^ symbol: class ProxyAuthenticate location: class SipSessionGroup.SipSessionImpl
第 5 步:检查运行时行为
在此步骤中,您将验证运行时行为是否符合预期。对于可调试的应用,您可以使用 StrictMode.detectNonSdkApiUsage
(在应用使用隐藏 API 时生成日志)监控隐藏的 API 使用情况。或者,您也可以使用 veridex 静态分析工具获取使用类型(链接或反射)、限制级别和调用堆栈。
veridex 语法:
./art/tools/veridex/appcompat.sh --dex-file={apk file}
veridex 结果示例:
#1: Linking greylist-max-o Landroid/animation/AnimationHandler;-><init>()V use(s): Lcom/android/systemui/pip/phone/PipMotionHelper;-><init>(Landroid/content/Context;Landroid/app/IActivityManager;Landroid/app/IActivityTaskManager;Lcom/android/systemui/pip/phone/PipMenuActivityController;Lcom/android/internal/policy/PipSnapAlgorithm;Lcom/android/systemui/statusbar/FlingAnimationUtils;)V #1332: Reflection greylist Landroid/app/Activity;->mMainThread use(s): Landroidx/core/app/ActivityRecreator;->getMainThreadField()Ljava/lang/reflect/Field;
如需详细了解 veridex 使用情况,请参阅使用 veridex 工具进行测试。
第 6 步:更新 device.mk
修复所有构建和运行时失败,并验证运行时行为是否符合预期后,请在 device.mk
中设置以下内容:
PRODUCT_PRODUCT_VNDK_VERSION := current
PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true