供应商 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 依赖项的稳定原生接口包括具有 stubs 和 LLNDK 库的 cc_library。这些依赖项已从打包中排除,并会记录在 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 fragment 和 init 脚本。

VINTF fragment

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

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

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

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

查询 API

将 VINTF fragment 添加到 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", ...):用于打开直通式 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

必须明确安装内核模块。以下示例是 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 之前,keymint 等一些 HAL 服务应可供使用。这些 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> - 如果设置了此字段,则会从 APEX 加载 Vulkan 驱动程序。由于早期 HAL 使用 Vulkan 驱动程序,因此请引导 APEX 并将该链接器命名空间配置为可见。

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

额外的开发功能

启动时的 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 的文件名。

// 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

设备启动后,通过设置永久性 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";