供应商 APEX

您可以使用 APEX 文件格式 来打包和安装较低级别的 Android 操作系统模块。它允许独立构建和安装组件,例如原生服务和库、HAL 实现、固件、配置文件等。

供应商 APEX 由构建系统自动安装在 /vendor 分区中,并由 apexd 在运行时激活,就像其他分区中的 APEX 一样。

用例

供应商镜像的模块化

APEX 有助于供应商镜像上功能实现的自然捆绑和模块化。

当供应商镜像构建为独立构建的供应商 APEX 的组合时,设备制造商能够轻松选择设备上所需的特定供应商实现。如果提供的 APEX 都不能满足他们的需求,或者他们有全新的自定义硬件,制造商甚至可以创建一个新的供应商 APEX。

例如,OEM 可以选择使用 AOSP wifi 实现 APEX、SoC 蓝牙实现 APEX 和自定义 OEM 电话实现 APEX 来组成他们的设备。

如果没有供应商 APEX,在供应商组件之间有如此多依赖关系的实现需要仔细的协调和跟踪。通过将所有组件(包括配置文件和额外的库)包装在 APEX 中,并在任何跨功能通信点处具有明确定义的接口,不同的组件变得可互换。

开发者迭代

通过将整个功能实现(例如 wifi HAL)捆绑在供应商 APEX 中,供应商 APEX 帮助开发者在开发供应商模块时更快地迭代。然后,开发者可以构建并单独推送供应商 APEX 以测试更改,而无需重建整个供应商镜像。

这简化并加快了主要在一个功能领域工作并希望仅在该功能领域进行迭代的开发者的开发者迭代周期。

将功能领域自然捆绑到 APEX 中也简化了构建、推送和测试该功能领域更改的过程。例如,重新安装 APEX 会自动更新 APEX 包含的任何捆绑库或配置文件。

将功能领域捆绑到 APEX 中也简化了在观察到不良设备行为时进行调试或还原的过程。例如,如果电话功能在新版本中运行不佳,则开发者可以尝试在设备上安装旧的电话实现 APEX(无需刷写完整版本)并查看是否恢复了良好行为。

示例工作流程

# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w

# Test the device.
... testing ...

# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...

# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...

示例

基础知识

有关通用 APEX 信息(包括设备要求、文件格式详细信息和安装步骤),请参阅主 APEX 文件格式 页面。

Android.bp 中,设置 vendor: true 属性会使 APEX 模块成为供应商 APEX。

apex {
  ..
  vendor: true,
  ..
}

二进制文件和共享库

APEX 在 APEX 有效负载中包含传递依赖项,除非它们具有稳定的接口。

供应商 APEX 依赖项的稳定原生接口包括带有 stubscc_library 和 LLNDK 库。这些依赖项不包含在打包中,依赖项记录在 APEX 清单中。清单由 linkerconfig 处理,以便外部原生依赖项在运行时可用。

在以下代码段中,APEX 同时包含二进制文件 (my_service) 及其非稳定依赖项(*.so 文件)。

apex {
  ..
  vendor: true,
  binaries: ["my_service"],
  ..
}

在以下代码段中,APEX 包含共享库 my_standalone_lib 及其任何非稳定依赖项(如上所述)。

apex {
  ..
  vendor: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

缩小 APEX

APEX 可能会因为捆绑非稳定依赖项而变大。我们建议使用静态链接。像 libc++.solibbase.so 这样的常用库可以静态链接到 HAL 二进制文件。使依赖项提供稳定的接口可能是另一种选择。依赖项将不会捆绑在 APEX 中。

HAL 实现

要定义 HAL 实现,请在供应商 APEX 中提供相应的二进制文件和库,类似于以下示例

为了完全封装 HAL 实现,APEX 还应指定任何相关的 VINTF 片段和 init 脚本。

VINTF 片段

当片段位于 APEX 的 etc/vintf 中时,可以从供应商 APEX 提供 VINTF 片段。

使用 prebuilts 属性将 VINTF 片段嵌入到 APEX 中。

apex {
  ..
  vendor: true,
  prebuilts: ["fragment.xml"],
  ..
}

prebuilt_etc {
  name: "fragment.xml",
  src: "fragment.xml",
  sub_dir: "vintf",
}

查询 API

当 VINTF 片段添加到 APEX 时,使用 libbinder_ndk API 获取 HAL 接口和 APEX 名称的映射。

  • AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default") : 如果 HAL 实例在 APEX 中定义,则为 true
  • AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...) : 获取定义 HAL 实例的 APEX 名称。
  • AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...) : 使用此 API 打开一个直通 HAL。

Init 脚本

APEX 可以通过两种方式包含 init 脚本:(A) APEX 有效负载中的预构建文本文件,或 (B) /vendor/etc 中的常规 init 脚本。您可以为同一个 APEX 设置两者。

APEX 中的 Init 脚本

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

供应商 APEX 中的 Init 脚本可以具有 service 定义和 on <property or event> 指令。

确保 service 定义指向同一 APEX 中的二进制文件。例如,com.android.foo APEX 可以定义一个名为 foo-service 的服务。

on foo-service /apex/com.android.foo/bin/foo
  ...

使用 on 指令时要小心。由于 APEX 中的 init 脚本在 APEX 激活 *之后* 解析和执行,因此某些事件或属性无法使用。使用 apex.all.ready=true 以尽早触发操作。引导 APEX 可以使用 on init,但不能使用 on early-init

固件

示例

使用 prebuilt_firmware 模块类型将固件嵌入到供应商 APEX 中,如下所示。

prebuilt_firmware {
  name: "my.bin",
  src: "path_to_prebuilt_firmware",
  vendor: true,
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.bin"],  // installed inside APEX as /etc/firmware/my.bin
  ..
}

prebuilt_firmware 模块安装在 APEX 的 <apex name>/etc/firmware 目录中。ueventd 扫描 /apex/*/etc/firmware 目录以查找固件模块。

APEX 的 file_contexts 应正确标记任何固件有效负载条目,以确保 ueventd 在运行时可以访问这些文件;通常,vendor_file 标签就足够了。例如

(/.*)? u:object_r:vendor_file:s0

内核模块

将内核模块作为预构建模块嵌入到供应商 APEX 中,如下所示。

prebuilt_etc {
  name: "my.ko",
  src: "my.ko",
  vendor: true,
  sub_dir: "modules"
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.ko"],  // installed inside APEX as /etc/modules/my.ko
  ..
}

APEX 的 file_contexts 应正确标记任何内核模块有效负载条目。例如

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

内核模块必须显式安装。以下供应商分区中的示例 init 脚本显示了通过 insmod 安装

my_init.rc:

on early-boot
  insmod /apex/myapex/etc/modules/my.ko
  ..

运行时资源叠加

示例

使用 rros 属性在供应商 APEX 中嵌入 运行时资源叠加

runtime_resource_overlay {
    name: "my_rro",
    soc_specific: true,
}


apex {
  ..
  vendor: true,
  rros: ["my_rro"],  // installed inside APEX as /overlay/my_rro.apk
  ..
}

其他配置文件

供应商 APEX 支持通常在供应商分区上找到的各种其他配置文件,作为供应商 APEX 内的预构建文件,并且正在添加更多。

示例

引导供应商 APEX

某些 HAL 服务(如 keymint)应在 APEX 激活之前可用。这些 HAL 通常在其 init 脚本的服务定义中设置 early_hal。另一个示例是 animation 类,它通常比 post-fs-data 事件更早启动。当这样的早期 HAL 服务打包在供应商 APEX 中时,请在其 APEX 清单中将 apex 设置为 "vendorBootstrap": true,以便可以更早地激活它。请注意,引导 APEX 只能从预构建位置(如 /vendor/apex)激活,而不能从 /data/apex 激活。

系统属性

这些是框架读取以支持供应商 APEX 的系统属性

  • input_device.config_file.apex=<apex name> - 设置后,将从 APEX 的 /etc/usr 目录中搜索输入配置文件(*.idc*.kl*.kcm)。
  • ro.vulkan.apex=<apex name> - 设置后,Vulkan 驱动程序将从 APEX 加载。由于 Vulkan 驱动程序被早期 HAL 使用,因此使 APEX 成为 引导 APEX 并配置该链接器命名空间可见。

使用 setprop 命令在 init 脚本 中设置系统属性。

额外的开发功能

启动时 APEX 选择

示例

开发者还可以安装共享相同 APEX 名称和密钥的多个版本的供应商 APEX,然后使用持久性系统属性选择在每次启动期间激活哪个版本。对于某些开发者用例,这可能比使用 adb install 安装 APEX 的新副本更简单。

示例用例

  • 安装 3 个版本的 wifi HAL 供应商 APEX: 质量保证团队可以使用一个版本运行手动或自动化测试,然后重启到另一个版本并重新运行测试,然后比较最终结果。
  • 安装 2 个版本的相机 HAL 供应商 APEX,当前版本实验版本 内部测试人员可以使用实验版本,而无需下载和安装额外的文件,因此他们可以轻松地换回。

在启动期间,apexd 查找遵循特定格式的系统属性以激活正确的 APEX 版本。

属性键的预期格式为

  • Bootconfig
    • 用于设置默认值,在 BoardConfig.mk 中。
    • androidboot.vendor.apex.<apex name>
  • 持久性系统属性
    • 用于更改默认值,在已启动的设备上设置。
    • 如果存在,则覆盖 bootconfig 值。
    • persist.vendor.apex.<apex name>

属性的值应为应激活的 APEX 的文件名。

// Default version.
apex {
  name: "com.oem.camera.hal.my_apex_default",
  vendor: true,
  ..
}

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  vendor: true,
  ..
}

默认版本也应使用 BoardConfig.mk 中的 bootconfig 进行配置

# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
    androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default

设备启动后,通过设置持久性系统属性更改激活的版本

$ adb root;
$ adb shell setprop \
    persist.vendor.apex.com.oem.camera.hal \
    com.oem.camera.hal.my_apex_experimental;
$ adb reboot;

如果设备支持在刷写后更新 bootconfig(例如通过 fastboot oem 命令),则更改多安装 APEX 的 bootconfig 属性也会更改启动时激活的版本。

对于基于 Cuttlefish 的虚拟参考设备,您可以使用 --extra_bootconfig_args 命令在启动时直接设置 bootconfig 属性。例如

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";