Android 11 取消捆绑了 product
分区,使其独立于 system
和 vendor
分区。作为这些变更的一部分,您现在可以控制 product
分区对原生和 Java 接口的访问权限(这类似于 vendor
分区的接口强制执行的工作方式)。
强制执行原生接口
要启用原生接口强制执行,请将 PRODUCT_PRODUCT_VNDK_VERSION
设置为 current
。(当目标的发布 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
文件中具有product_available: true
的模块可用于产品变体。指定
product_specific: true
的库或二进制文件可以链接到其他库,这些库在其Android.bp
文件中指定product_specific: true
或product_available: true
。VNDK 库必须在其
Android.bp
文件中具有product_available: true
,以便product
二进制文件可以链接到 VNDK 库。
下表总结了用于创建映像变体的 Android.bp
属性。
Android.bp 中的属性 | 创建的变体 | |
---|---|---|
强制执行前 | 强制执行后 | |
默认 (无) | 核心 (包括 /system 、/system_ext 和 /product ) |
核心 (包括 /system 和 /system_ext ,但不包括 /product ) |
system_ext_specific: true |
核心 | 核心 |
product_specific: true |
核心 | 产品 |
vendor: true |
供应商 | 供应商 |
vendor_available: true |
核心、供应商 | 核心、供应商 |
product_available: true |
不适用 | 核心、产品 |
vendor_available: true AND product_available: true |
不适用 | 核心、产品、供应商 |
system_ext_specific: true AND vendor_available: true |
核心、供应商 | 核心、供应商 |
product_specific: true AND 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
。(当目标的发布 API 级别高于 29 时,该值会自动设置为 true
。)启用后,强制执行允许或禁止以下访问
API | /system | /system_ext | /product | /vendor | /data |
---|---|---|---|---|---|
公共 API | |||||
@SystemApi | |||||
@hide API |
与 vendor
分区中一样,product
分区中的应用或 Java 库仅允许使用公共 API 和系统 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_specific: true
。 - 模块可能缺少产品模块所需的产品变体。要修复,请通过设置
product_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