供應商 APEX

您可以使用 APEX 檔案格式封裝及安裝較低層級的 Android 作業系統模組。可獨立建構及安裝元件,例如原生服務和程式庫、HAL 實作項目、韌體、設定檔等。

建構系統會自動在 /vendor 分區中安裝供應商 APEX,並在執行階段由 apexd 啟動,就像其他分區中的 APEX 一樣。

用途

供應商圖片模組化

APEX 可在供應商映像檔上,自然地將功能實作項目組合在一起並模組化。

如果供應商映像檔是透過獨立建構的供應商 APEX 組合建構而成,裝置製造商就能輕鬆選擇要在裝置上使用的特定供應商實作項目。如果提供的 APEX 都不符合需求,或是有全新的自訂硬體,製造商甚至可以建立新的供應商 APEX。

舉例來說,原始設備製造商可能會選擇使用 AOSP Wi-Fi 實作項目 APEX、SoC 藍牙實作項目 APEX,以及自訂原始設備製造商電話實作項目 APEX,組成自己的裝置。

如果沒有供應商 APEX,在供應商元件之間有這麼多依附元件的情況下,實作作業需要仔細協調及追蹤。在 APEX 中包裝所有元件 (包括設定檔和額外程式庫),並在跨功能通訊的任何時間點清楚定義介面,即可互換不同元件。

開發人員疊代

供應商 APEX 可將整個功能實作 (例如 Wi-Fi HAL) 綁定在供應商 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 依附元件的穩定原生介面包括 cc_library (含 stubs) 和 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 片段

如果 VINTF 片段位於 APEX 的 etc/vintf 中,即可從供應商 APEX 提供這些片段。

使用 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")true 如果 HAL 執行個體是在 APEX 中定義。
  • 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

核心模組必須明確安裝。以下範例是供應商分割區中的初始化指令碼,說明如何透過 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

啟用 APEX 前,應先提供部分 HAL 服務,例如 keymint。這些 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 Bootstrap APEX,並設定可見的連結器命名空間。

使用 setprop 指令,在初始化指令碼中設定系統屬性。

額外功能

啟動時選取 APEX

範例:

您可以在啟動期間選擇啟用供應商 APEX。如果您使用系統屬性 ro.vendor.apex.<apex name> 指定檔案名稱,系統只會為特定 <apex name> 啟用與檔案名稱相符的 APEX。如果這個系統屬性設為 none,系統會忽略 (不啟用) 含有 <apex name> 的 APEX。您可以使用這項功能安裝多個同名 APEX。如果同一個 APEX 有多個版本,這些版本應共用相同的金鑰。

應用情境範例:

  • 安裝 3 個版本的 Wi-Fi HAL 供應商 APEX:品質保證團隊可以使用其中一個版本執行手動或自動化測試,然後重新啟動至另一個版本並重新執行測試,最後比較結果。
  • 安裝 2 個版本的相機 HAL 供應商 APEX,分別是目前實驗性版本:Dogfooders 可以使用實驗性版本,不必下載及安裝額外檔案,因此可以輕鬆切換回原版本。

啟動期間,apexd 會尋找特定格式的 sysprop,以啟動正確的 APEX 版本。

屬性鍵的預期格式為:

  • Bootconfig
    • 用於在 BoardConfig.mk 中設定預設值。
    • androidboot.vendor.apex.<apex name> (匯出為 ro.boot.vendor.apex.<apex name>)。
  • 動態早期啟動設定 (init_dev_config):
    • 建議使用的正式版機制,可啟用及停用每個 SKU 的 APEX (適用於 Android 17 以上版本)。
    • apexd-bootstrap 執行前,於啟動時動態設定 ro.boot.vendor.apex.<apex name>。將屬性值設為 none 可明確停用 APEX 啟動。
    • 如需架構詳細資料,請參閱「提早啟動裝置設定 (init_dev_config)」。
  • 永久 sysprop (已淘汰):
    • persist.vendor.apex.<apex name>
    • 這項功能僅供開發用途。由於 apexd 會在掛接 /data 分區前啟動預先安裝的 APEX,因此儲存在 /data 中的永久性系統屬性無法用於正式版建構中的早期 APEX 選取作業。

屬性的值應為要啟用的 APEX 檔案名稱,或 none 來停用 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,
  ..
}

使用 bootconfig 選取 APEX

如要在建構時宣告要使用的 APEX,請在 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

如果裝置支援在刷機後更新啟動設定 (例如使用 fastboot oem 指令),變更多重安裝 APEX 的啟動設定屬性也會變更啟動時啟用的版本。

如果是以 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";

使用 init_dev_config 選取 APEX

在 Android 17 以上版本中,如要在執行階段動態選取或停用供應商 APEX,請實作由 init_dev_config 服務執行的供應商工具。這項工具會在 early-init 期間執行,然後在 apexd-bootstrap 之前使用 rustutils::system_properties 設定 ro.boot.vendor.apex.* 屬性。

將屬性設為 APEX 的檔案名稱,即可選取要啟用的特定版本;將值設為 none 則可明確停用 APEX 啟用作業,例如在沒有硬體周邊裝置的 SKU 上。

use rustutils::system_properties;

fn main() {
    // Select an APEX version based on detected SKU, or disable it altogether:
    let hal_apex = match sku_type {
        1 => "com.oem.camera.hal.my_apex_default",
        2 => "com.oem.camera.hal.my_apex_experimental",
        _ => "none",
    };
    system_properties::write(
        "ro.boot.vendor.apex.com.oem.camera.hal",
        hal_apex,
    ).expect("Failed to set vendor APEX selection property");
}

使用永久性系統屬性進行開發 (已淘汰)

為進行開發和偵錯,裝置啟動後,您可以設定持續性系統屬性,變更啟用的版本:

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