HAL 適用的 AIDL

Android 11 推出了在 Android 中使用 AIDL 的 HAL 功能。這樣一來,您就能在不使用 HIDL 的情況下,實作部分 Android 功能。盡可能將 HAL 轉換為專用 AIDL (當上游 HAL 使用 HIDL 時,必須使用 HIDL)。

使用 AIDL 在架構元件 (例如 system.img 中的元件) 與硬體元件 (例如 vendor.img 中的元件) 之間通訊的 HAL,必須使用穩定版 AIDL。不過,如果要在分區內進行通訊 (例如從一個 HAL 傳送至另一個 HAL),則可使用的 IPC 機制不受限制。

動機

AIDL 的使用時間比 HIDL 還要久,而且用途廣泛,例如在 Android 架構元件之間或應用程式中使用。由於 AIDL 提供穩定性支援,因此可以使用單一 IPC 執行階段實作整個堆疊。AIDL 的版本管理系統也比 HIDL 更完善。

  • 使用單一 IPC 語言,表示您只需要學習、偵錯、最佳化和保護單一項目。
  • AIDL 可為介面擁有者支援原地版本控制:
    • 擁有者可以將方法新增至介面的結尾,或將欄位新增至可分割的項目。這表示您日後更容易為程式碼命名,而且每年的成本也會降低 (類型可就地修正,且每個介面版本都不需要額外的程式庫)。
    • 擴充功能介面可在執行階段附加,而非在類型系統中附加,因此不需要將下游擴充功能重新連結至較新版本的介面。
  • 當擁有者選擇穩定現有的 AIDL 介面時,可以直接使用該介面。在此之前,您必須在 HIDL 中建立介面的完整副本。

以 AIDL 執行階段為基礎進行建構

AIDL 有三種不同的後端:Java、NDK、CPP。如要使用 Stable AIDL,您必須一律使用 libbinder 的系統副本,並在 system/lib*/libbinder.so/dev/binder 上進行通訊。對於供應商映像檔中的程式碼,這表示無法使用 libbinder (來自 VNDK):這個程式庫具有不穩定的 C++ API 和不穩定的內部元件。相反地,原生供應商程式碼必須使用 AIDL 的 NDK 後端,並連結至 libbinder_ndk (由系統 libbinder.so 提供支援),以及由 aidl_interface 項目建立的 NDK 程式庫。如需確切的模組名稱,請參閱模組命名規則

編寫 AIDL HAL 介面

如要在系統和供應商之間使用 AIDL 介面,則需要進行兩項變更:

  • 每個類型定義都必須加上 @VintfStability 註解。
  • aidl_interface 宣告必須包含 stability: "vintf",

只有介面擁有者才能進行這些變更。

進行這些變更時,介面必須位於 VINTF 資訊清單中才能運作。使用 VTS 測試 vts_treble_vintf_vendor_test 測試這項功能 (以及相關需求,例如驗證已發布的介面是否已凍結)。您可以使用 @VintfStability 介面,而無需符合這些需求,方法是在繫結器物件傳送至其他程序之前,在 NDK 後端呼叫 AIBinder_forceDowngradeToLocalStability、在 C++ 後端呼叫 android::Stability::forceDowngradeToLocalStability,或在 Java 後端呼叫 android.os.Binder#forceDowngradeToSystemStability。Java 不支援將服務降級至供應商穩定性,因為所有應用程式都會在系統情境中執行。

此外,為盡可能提高程式碼可移植性,並避免不必要的額外程式庫等潛在問題,請停用 CPP 後端。

請注意,下方程式碼範例中的 backends 用法是正確的,因為有三個後端 (Java、NDK 和 CPP)。以下程式碼說明如何具體選取 CPP 後端,以便停用該後端。

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

尋找 AIDL HAL 介面

HAL 的 AOSP 穩定版 AIDL 介面與 HIDL 介面位於相同的基礎目錄 (aidl 資料夾) 中。

  • hardware/interfaces:通常由硬體提供的介面
  • frameworks/hardware/interfaces:針對硬體提供的高階介面
  • system/hardware/interfaces:適用於為硬體提供的低階介面

您應將擴充功能介面放入 vendorhardware 中的其他 hardware/interfaces 子目錄。

擴充功能介面

Android 每個版本都提供一組官方 AOSP 介面。當 Android 合作夥伴想要為這些介面新增功能時,不應直接變更這些介面,否則 Android 執行階段就會與 AOSP Android 執行階段不相容。對於 GMS 裝置,避免變更這些介面,也是確保 GSI 映像檔能繼續運作的做法。

擴充功能可以透過兩種方式註冊:

不過,當供應商專屬 (意即非上游 AOSP 的一部分) 元件使用介面時,註冊的擴充功能就不會發生合併衝突。不過,如果對上游 AOSP 元件進行下游修改,可能會導致合併衝突,因此建議採用下列策略:

  • 新增的介面可在下一個版本中上游至 AOSP
  • 可進一步提供彈性且不會產生合併衝突的介面新增項目,可在下一個版本中上游

擴充功能 Parcelable:ParcelableHolder

ParcelableHolder 是可包含其他 ParcelableParcelableParcelableHolder 的主要用途是讓 Parcelable 可擴充。舉例來說,裝置實作者希望能夠擴充 AOSP 定義的 Parcelable (AospDefinedParcelable),以納入附加價值功能。

先前在沒有 ParcelableHolder 的情況下,裝置實作者無法修改 AOSP 定義的穩定 AIDL 介面,因為新增更多欄位會發生錯誤:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

如前述程式碼所示,這種做法會失效,因為裝置實作者在下一個 Android 版本中修訂 Parcelable 時,所新增的欄位可能會發生衝突。

使用 ParcelableHolder,可讓可分割的擁有者在 Parcelable 中定義擴充點。

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

接著,裝置實作人員可以為擴充功能定義自己的 Parcelable

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

最後,您可以使用 ParcelableHolder 欄位,將新的 Parcelable 附加至原始 Parcelable


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

AIDL HAL 伺服器執行個體名稱

按照慣例,AIDL HAL 服務的例項名稱格式為 $package.$type/$instance。舉例來說,振動器 HAL 的例項會註冊為 android.hardware.vibrator.IVibrator/default

編寫 AIDL HAL 伺服器

@VintfStability AIDL 伺服器必須在 VINTF 資訊清單中宣告,例如:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

否則,他們應該會正常註冊 AIDL 服務。執行 VTS 測試時,所有已宣告的 AIDL HAL 都應可用。

編寫 AIDL 用戶端

AIDL 用戶端必須在相容性矩陣中宣告自己,例如:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

將現有的 HAL 從 HIDL 轉換為 AIDL

使用 hidl2aidl 工具將 HIDL 介面轉換為 AIDL。

hidl2aidl 功能:

  • 根據指定套件的 .hal 檔案建立 .aidl 檔案
  • 為新建的 AIDL 套件建立建構規則,並啟用所有後端
  • 在 Java、CPP 和 NDK 後端中建立翻譯方法,以便將 HIDL 類型轉換為 AIDL 類型
  • 為含有必要依附元件的翻譯程式庫建立建構規則
  • 建立靜態斷言,確保 HIDL 和 AIDL 列舉器在 CPP 和 NDK 後端中具有相同的值

請按照下列步驟將 .hal 檔案套件轉換為 .aidl 檔案:

  1. 建構位於 system/tools/hidl/hidl2aidl 的工具。

    從最新來源建構這項工具,可提供最完整的體驗。您可以使用最新版本,將舊分支版本中的介面轉換為先前版本。

    m hidl2aidl
  2. 執行工具時,請附上輸出目錄,並接著附上要轉換的套件。

    您可以選擇使用 -l 引數,將新授權檔案的內容新增至所有產生的檔案頂端。請務必使用正確的執照和日期。

    hidl2aidl -o <output directory> -l <file with license> <package>

    例如:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. 請詳閱產生的檔案,並修正轉換作業中出現的任何問題。

    • conversion.log 包含所有需要優先修正的未處理問題。
    • 產生的 .aidl 檔案可能會出現警告和建議,需要採取行動。這些註解開頭為 //
    • 請把握機會清理並改善套件。
    • 檢查 @JavaDerive 註解,找出可能需要的功能,例如 toStringequals
  4. 只建構所需的目標。

    • 停用不會使用的後端。請優先使用 NDK 後端,而非 CPP 後端,請參閱「選擇執行階段」。
    • 移除不會使用的翻譯程式庫或產生的程式碼。
  5. 請參閱「主要 AIDL/HIDL 差異」。

    • 使用 AIDL 內建的 Status 和例外狀況通常可改善介面,並免除需要其他特定介面狀態類型的情況。
    • 方法中的 AIDL 介面引數預設為 @nullable,而非 HIDL 中的引數。

AIDL HAL 的 SEPolicy

供應商程式碼可見的 AIDL 服務類型必須具備 hal_service_type 屬性。否則,sepolicy 設定與任何其他 AIDL 服務相同 (但 HAL 有特殊屬性)。以下是 HAL 服務內容的定義範例:

    type hal_foo_service, service_manager_type, hal_service_type;

對於平台定義的大部分服務,系統已新增具有正確類型的服務內容 (例如,android.hardware.foo.IFoo/default 已標示為 hal_foo_service)。不過,如果架構用戶端支援多個執行個體名稱,則必須在特定裝置的 service_contexts 檔案中新增其他執行個體名稱。

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

建立新類型的 HAL 時,必須新增 HAL 屬性。特定 HAL 屬性可能會與多種服務類型建立關聯 (如剛才所述,每個服務類型可能會有多個例項)。對於 HAL foo,我們有 hal_attribute(foo)。這個巨集定義了 hal_foo_clienthal_foo_server 屬性。針對特定網域,hal_client_domainhal_server_domain 巨集會將網域與特定 HAL 屬性建立關聯。舉例來說,系統伺服器是此 HAL 的用戶端,對應的政策為 hal_client_domain(system_server, hal_foo)。HAL 伺服器同樣包含 hal_server_domain(my_hal_domain, hal_foo)。通常,對於特定 HAL 屬性,我們也會建立 hal_foo_default 等網域,用於參考或示例 HAL。不過,部分裝置會將這些網域用於自家的伺服器。只有在有多個伺服器提供相同介面,且需要在實作中使用不同的權限集合時,才需要區分多個伺服器的網域。在所有這些巨集中,hal_foo 實際上並非 sepolicy 物件。相反地,這些巨集會使用這個符記,參照與用戶端伺服器組合相關聯的一組屬性。

不過,到目前為止,我們並未將 hal_foo_servicehal_foo (來自 hal_attribute(foo) 的屬性組合) 建立關聯。HAL 屬性會使用 hal_attribute_service 巨集與 AIDL HAL 服務建立關聯 (HIDL HAL 會使用 hal_attribute_hwservice 巨集)。例如:hal_attribute_service(hal_foo, hal_foo_service)。這表示 hal_foo_client 程序可以取得 HAL,而 hal_foo_server 程序可以註冊 HAL。系統會透過內容管理工具 (servicemanager) 執行這些註冊規則。請注意,服務名稱不一定會對應至 HAL 屬性。舉例來說,我們可能會看到 hal_attribute_service(hal_foo, hal_foo2_service)。不過,由於這表示服務一律會一起使用,因此我們可以考慮移除 hal_foo2_service,並在所有服務情境中使用 hal_foo_service。大多數設定多個 hal_attribute_service 的 HAL 是因為原始 HAL 屬性名稱不夠通用,且無法變更。

將所有內容整合在一起,HAL 範例會像這樣:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

已連結的擴充功能介面

擴充功能可附加至任何繫結器介面,無論是直接向服務管理員註冊的頂層介面,還是子介面皆可。取得擴充功能時,您必須確認擴充功能的類型是否符合預期。您只能透過提供繫結器的程序設定擴充功能。

每當擴充功能修改現有 HAL 的功能時,都應使用附加式擴充功能。如需完全不同的功能,則不必使用這個機制,而且可以直接向服務管理員註冊擴充功能介面。附加的擴充功能介面最適合附加至子介面,因為這些階層可能會深層或多重執行個體。使用全域擴充功能來鏡像另一項服務的 Binder 介面階層,需要大量記錄才能為直接附加的擴充功能提供等效功能。

如要設定繫結器的擴充功能,請使用下列 API:

  • 在 NDK 後端:AIBinder_setExtension
  • 在 Java 後端:android.os.Binder.setExtension
  • 在 CPP 後端:android::Binder::setExtension
  • 在 Rust 後端:binder::Binder::set_extension

如要取得繫結器的擴充功能,請使用下列 API:

  • 在 NDK 後端:AIBinder_getExtension
  • 在 Java 後端:android.os.IBinder.getExtension
  • 在 CPP 後端:android::IBinder::getExtension
  • 在 Rust 後端:binder::Binder::get_extension

如要進一步瞭解這些 API,請參閱對應後端的 getExtension 函式說明文件。如需使用擴充功能的範例,請參閱 hardware/interfaces/tests/extension/vibrator

AIDL 和 HIDL 的主要差異

使用 AIDL HAL 或 AIDL HAL 介面時,請注意與編寫 HIDL HAL 的差異。

  • AIDL 語言的語法與 Java 更為接近。HIDL 語法類似 C++。
  • 所有 AIDL 介面都有內建錯誤狀態。請勿建立自訂狀態類型,而是在介面檔案中建立常數狀態 int,並在 CPP/NDK 後端使用 EX_SERVICE_SPECIFIC,在 Java 後端使用 ServiceSpecificException。請參閱「錯誤處理」一節。
  • 傳送 Binder 物件時,AIDL 不會自動啟動執行緒集區。必須手動啟動 (請參閱「執行緒管理」)。
  • AIDL 不會在未檢查的傳輸錯誤中中止 (HIDL Return 會在未檢查的錯誤中中止)。
  • AIDL 每個檔案只能宣告一種型別。
  • 除了輸出參數之外,您也可以將 AIDL 引數指定為 in/out/inout (沒有「同步回呼」)。
  • AIDL 會使用 fd 做為原始類型,而非句柄。
  • HIDL 會將主要版本用於不相容的變更,次要版本則用於相容的變更。在 AIDL 中,回溯相容的變更會在原地完成。AIDL 沒有明確的主要版本概念,而是將其納入套件名稱中。舉例來說,AIDL 可能會使用套件名稱 bluetooth2
  • 根據預設,AIDL 不會繼承即時優先順序。必須為每個繫結器使用 setInheritRt 函式,才能啟用即時優先順序繼承功能。

供應商測試套件 (VTS) 針對 HAL 進行的測試

Android 會使用供應商測試套件 (VTS) 驗證預期的 HAL 實作方式。VTS 可確保 Android 與舊版供應商實作回溯相容。未通過 VTS 的實作項目有已知的相容性問題,可能無法與未來版本的作業系統搭配運作。

HAL 的 VTS 有兩個主要部分。

1. 確認裝置上的 HAL 是 Android 所知且預期的。

這組測試可在 test/vts-testcase/hal/treble/vintf 中找到。負責驗證以下項目:

  • 在 VINTF 資訊清單中宣告的每個 @VintfStability 介面都會在已知的發布版本中凍結。這可確保介面雙方對於該介面版本的確切定義達成共識。這是基本操作的必要條件。
  • 在 VINTF 資訊清單中宣告的所有 HAL,皆可在該裝置上使用。任何具備使用已宣告 HAL 服務的充分權限的用戶端,必須能夠隨時取得及使用這些服務。
  • 在 VINTF 資訊清單中宣告的所有 HAL,都會提供資訊清單中宣告的介面版本。
  • 裝置上沒有任何已淘汰的 HAL。Android 將停止支援舊版 HAL 介面,如FCM 生命週期所述。
  • 裝置上有必要的 HAL。部分 HAL 是 Android 正常運作所需的。

2. 驗證每個 HAL 的預期行為

每個 HAL 介面都有專屬的 VTS 測試,可驗證其用戶端的預期行為。測試案例會針對已宣告 HAL 介面的每個例項執行,並根據實作的介面版本強制執行特定行為。

這些測試會嘗試涵蓋 Android 架構所依賴或日後可能會依賴的 HAL 實作項目。

這些測試包括驗證功能支援、錯誤處理,以及用戶端可能期望服務提供的任何其他行為。

HAL 開發作業的 VTS 里程碑

建立或修改 Android 的 HAL 介面時,VTS 測試應保持最新狀態。

必須先完成 VTS 測試,並準備驗證供應商實作項目,才能凍結 Android 供應商 API 版本。這些檔案必須在介面凍結前準備就緒,以便開發人員建立實作項目、驗證實作項目,並向 HAL 介面開發人員提供意見回饋。

烏賊的 VTS

當無法使用硬體時,Android 會使用 Cuttlefish 做為 HAL 介面的開發工具。這可讓您在 Android 上進行可擴充的 VTS 和整合測試。hal_implementation_test 會測試 Cuttlefish 是否實作最新的 HAL 介面版本,確保 Android 可處理新介面,且 VTS 測試可在新的硬體和裝置推出後立即測試新的供應商實作項目。