供应商 APEX

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

您可以使用 APEX 文件格式打包和安装较低级别的 Android OS 模块。该模块支持单独构建和安装一些组件,例如原生服务和库、HAL 实现、固件以及配置文件等。

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

用例

对供应商映像进行模块化处理

APEX 有助于对供应商映像上的功能实现进行自然捆绑和模块化处理。

如果供应商映像是将单独构建的供应商 APEX 加以组合构建而成,设备制造商将能够轻松选择他们的设备所需的特定供应商实现。如果提供的 APEX 都不能满足需求,或者制造商拥有全新的自定义硬件,他们也可以创建新的供应商 APEX。

例如,原始设备制造商 (OEM) 可以选择使用 AOSP Wi-Fi 实现 APEX、SoC 蓝牙实现 APEX 和自定义 OEM 电话实现 APEX 来配置设备。

如果没有供应商 APEX,需要仔细协调和跟踪供应商组件之间具有许多依赖项的实现。通过在功能间通信的任何时间点使用明确定义的接口在 APEX 中封装所有组件(包括配置文件和其他库),不同组件将可以互换。

开发者迭代

供应商 APEX 会在供应商 APEX 内捆绑整个功能实现(例如 Wi-Fi HAL),从而帮助开发者在开发供应商模块时更快地迭代。然后,开发者可以构建并单独推送供应商 APEX 来测试更改,而不是重新构建整个供应商映像。

对于主要负责一个功能区域且希望只针对该功能区域进行迭代的开发者而言,这可以简化过程,并加速开发者迭代周期。

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

此外,将功能区域捆绑到 APEX 也可以简化在观察到不良设备行为时进行的调试或还原。例如,如果电话在新的 build 中无法正常运行,开发者可以尝试在设备上安装较早的电话实现 APEX(无需刷写整个 build),并查看是否已恢复正常运行。

工作流示例:

# 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_libraryndk_libraryllndk_library。这些依赖项已从打包中排除,并会记录在 APEX 清单中。清单由 linkerconfig 处理,因此外部原生依赖项在运行时可用。

/system 分区中的 APEX 不同,供应商 APEX 通常与特定 VNDK 版本相关联。VNDK 库可保证版本中的 ABI 稳定性,因此我们可以将 VNDK 库视为稳定,并使用 use_vndk_as_stable 属性将供应商 APEX 从 APEX 中排除,从而缩减供应商 APEX 的大小。

在以下代码段中,APEX 将包含二进制文件 (my_service) 及其非稳定依赖项(*.so 文件)。它不包含 VNDK 库,即使 my_service 是使用 VNDK 库(如 libbase)构建的,也是如此。然而,my_service 将在运行时使用系统提供的 VNDK 库中的 libbase

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

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

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

HAL 实现

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

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

VINTF fragment

VINTF fragment 安装在 /vendor/etc/vintf 中,而不是包含在 APEX 内。

使用标准 vintf_fragments 属性:

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

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> 指令。如果您的服务需要依赖于某些条件,您需要在 /vendor 中安装 init 脚本。

/vendor 中的 init 脚本:

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

对 init 脚本中二进制文件路径的任何引用都需要反映 APEX 内的路径。您可以对 init 脚本源文件中的路径进行硬编码,也可以使用 genrule 从现有源文件中替换它。

固件

示例

使用 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

必须明确安装内核模块。以下示例是 vendor 分区中的 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 内的 vendor 分区,之后还会添加更多配置文件。

示例

额外的开发功能

启动时的 APEX 选择

示例

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

用例示例:

  • 安装 3 个版本的 Wi-Fi HAL 供应商 APEX:QA 团队可以使用一个版本手动或自动运行测试,接着重新启动以使用另一个版本并重新运行测试,然后比较最终结果。
  • 安装 2 个版本的相机 HAL 供应商 APEX(当前版本和实验性版本):Dogfood 测试者可以使用实验性版本,而无需下载和安装其他文件,因此可以轻松切换回当前版本。

在启动过程中,apexd 会按照特定格式查找 sysprop,以激活正确的 APEX 版本。

属性键的预期格式如下:

  • bootconfig
    • 用于设置默认值,位于 BoardConfig.mk
    • androidboot.vendor.apex.<apex name>
  • 永久性 sysprop
    • 用于更改在已启动设备上设置的默认值。
    • 替换 bootconfig 值(如果存在)。
    • persist.vendor.apex.<apex name>

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

对 APEX Android.bp 定义的唯一更改是 multi_install_skip_symbol_files 字段,对于所有非默认且安装了多个版本的 APEX 定义,该字段应为 true

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

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  apex_name: "com.oem.camera.hal",
  vendor: true,
  multi_install_skip_symbol_files: 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

设备启动后,通过设置永久性 sysprop 更改已激活的版本:

$ 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";