HAL 專用的 AIDL

Android 11 導入了在 Android 中使用 AIDL 進行 HAL 的功能,因此可以實作 Android 的部分內容,而不需使用 HIDL。盡可能將 HAL 轉換為專門使用 AIDL (上游 HAL 使用 HIDL 時,必須使用 HIDL)。

使用 AIDL 在架構元件 (例如 system.img 中的元件) 和硬體元件 (例如 vendor.img 中的元件) 之間通訊的 HAL,必須使用穩定版 AIDL。不過,如要在分區內通訊 (例如從一個 HAL 到另一個 HAL),則可自由使用 IPC 機制。

動機

AIDL 的歷史比 HIDL 更悠久,且用於許多其他地方,例如 Android 架構元件之間或應用程式中。AIDL 現在支援穩定性,因此您可以使用單一 IPC 執行階段實作整個堆疊。AIDL 的版本控管系統也比 HIDL 更完善。AIDL 的優點包括:

  • 使用單一 IPC 語言表示您只需要學習、偵錯、最佳化及保護一項事物。
  • AIDL 支援介面擁有者的就地版本控管:
    • 擁有者可以在介面結尾新增方法,或在可打包物件中新增欄位。 這表示多年來程式碼的版本控管會更加容易,且年年成本較低 (可就地修正型別,且每個介面版本都不需要額外的程式庫)。
    • 擴充介面可在執行階段附加,而非在型別系統中附加,因此不需要將下游擴充功能重新設定為較新版本的介面。
  • 如果現有 AIDL 介面的擁有者選擇穩定介面,即可直接使用。過去,必須在 HIDL 中建立整個介面的副本。

以 AIDL 執行階段為基礎建構

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

編寫 AIDL HAL 介面

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

  • 每個型別定義都必須使用 @VintfStability 註解。
  • aidl_interface 聲明必須包含 stability: "vintf",

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

進行這些變更時,介面必須位於 VINTF 資訊清單中,才能正常運作。使用 Vendor Test Suite (VTS) 測試 vts_treble_vintf_vendor_test 測試這項要求 (以及相關要求,例如驗證發布的介面是否已凍結)。您可以在將繫結器物件傳送至其他程序之前,在 NDK 後端呼叫 AIBinder_forceDowngradeToLocalStability、在 C++ 後端呼叫 android::Stability::forceDowngradeToLocalStability,或在 Java 後端呼叫 android.os.Binder#forceDowngradeToSystemStability,藉此使用 @VintfStability 介面,不必符合上述規定。

此外,為確保程式碼可攜性達到最高水準,並避免不必要的額外程式庫等潛在問題,請停用 CPP 後端。

程式碼:如何停用 CPP 後端:

    aidl_interface: {
        ...
        backend: {
            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 執行階段不相容。請避免變更這些介面,確保 GSI 映像檔能繼續運作。

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

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

  • 在下一個版本中,將介面新增內容上傳至 AOSP。
  • 上游介面新增內容,可在下一個版本中提供更大的彈性 (不會發生合併衝突)。

擴充功能可 Parcelable:ParcelableHolder

ParcelableHolderParcelable 介面的執行個體,可包含另一個 Parcelable 執行個體。

ParcelableHolder 的主要用途是讓 Parcelable 可擴充。舉例來說,裝置實作人員希望能夠擴充 AOSP 定義的 ParcelableAospDefinedParcelable,納入自己的加值功能。

使用 ParcelableHolder 介面,透過加值功能擴充 ParcelableParcelableHolder 介面包含 Parcelable 的例項。如果嘗試將欄位新增至 Parcelable,就會發生錯誤:

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;
}

新的 Parcelable 例項可透過 ParcelableHolder 欄位附加至原始 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 (.hal) 檔案,建立 AIDL (.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 檔案可能含有需要處理的警告和建議。這些註解開頭為 //
    • 清理及改善套件。
    • 檢查可能需要的特徵 (例如 toStringequals) 的 @JavaDerive 註解。
  4. 只建立需要的目標:

    • 停用不會使用的後端。建議使用 NDK 後端,而非 CPP 後端;請參閱「依據 AIDL 執行階段建構」。
    • 移除不會使用的翻譯程式庫或任何產生的程式碼。
  5. 請參閱「AIDL 和 HIDL 的主要差異」:

    • 使用 AIDL 的內建 Status 和例外狀況,通常可以改善介面,並移除其他介面專屬的狀態類型。
    • 方法中的 AIDL 介面引數預設不會像 HIDL 一樣 @nullable

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,則為 foofoohal_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 巨集 (HIDL HAL 則使用 hal_attribute_hwservice 巨集) 與 AIDL HAL 服務建立關聯,例如 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 設定多個 hal_attribute_service 執行個體時,是因為原始 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 的功能,就請使用附加擴充功能。如果需要全新的功能,就不必使用這個機制,可以直接向服務管理員註冊擴充介面。附加擴充功能介面最適合附加至子介面,因為這些階層可能很深或有多個例項。使用全域擴充功能鏡像另一個服務的繫結器介面階層,需要大量記帳,才能提供與直接附加擴充功能同等的功能。

如要在繫結器上設定擴充功能,請使用下列 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 介面都有內建的錯誤狀態。請在介面檔案中建立常數狀態整數,並在 CPP 和 NDK 後端使用 EX_SERVICE_SPECIFIC,在 Java 後端使用 ServiceSpecificException,而不要建立自訂狀態類型。請參閱「錯誤處理」。
  • 傳送繫結器物件時,AIDL 不會自動啟動執行緒集區。 您必須手動啟動這些執行緒 (請參閱「執行緒管理」)。
  • AIDL 不會因未檢查的傳輸錯誤而中止 (HIDL Return 會因未檢查的錯誤而中止)。
  • 每個檔案只能宣告一種型別。
  • 除了輸出參數外,AIDL 引數也可以指定為 inoutinout (沒有同步回呼)。
  • AIDL 使用 fd 做為原始型別,而非 handle
  • HIDL 會針對不相容的變更使用主要版本,針對相容的變更使用次要版本。在 AIDL 中,回溯相容的變更會就地完成。 AIDL 沒有明確的主要版本概念,而是納入套件名稱。舉例來說,AIDL 可能會使用套件名稱 bluetooth2
  • AIDL 預設不會繼承即時優先順序。每個繫結器都必須使用 setInheritRt 函式,才能啟用即時優先權繼承。

HAL 測試

本節說明測試 HAL 的最佳做法。即使 HAL 的整合測試不在 VTS 中,這些做法也適用。

Android 會依賴 VTS 驗證預期的 HAL 實作方式。VTS 可確保 Android 能與舊版供應商實作項目回溯相容。如果實作項目未通過 VTS,表示有已知的相容性問題,可能無法與日後版本的作業系統搭配運作。

HAL 的 VTS 主要分為兩部分。

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

Android 必須有所有已安裝 HAL 的靜態準確清單。這份清單會以 VINTF 資訊清單表示。特殊的平台全域測試會一起驗證整個系統中 HAL 層的完整性。撰寫任何 HAL 專屬測試之前,您也應執行這些測試,因為這些測試可以判斷 HAL 是否有不一致的 VINTF 設定。

這組測試位於 test/vts-testcase/hal/treble/vintf。如果您正在開發供應商 HAL 實作項目,請使用 vts_treble_vintf_vendor_test 進行驗證。您可以使用 atest vts_treble_vintf_vendor_test 指令執行這項測試。

這些測試負責驗證:

  • VINTF 資訊清單中宣告的每個 @VintfStability 介面,都會凍結在已知的發布版本。這項作業會驗證介面兩端是否都同意該介面版本的確切定義。這是基本運作的必要條件。
  • VINTF 資訊清單中聲明的所有 HAL 都可在該裝置上使用。只要用戶端具備使用已宣告 HAL 服務的足夠權限,就必須隨時能夠取得及使用這些服務。
  • VINTF 資訊清單中宣告的所有 HAL 都會提供資訊清單中宣告的介面版本。
  • 裝置上沒有任何已淘汰的 HAL。如FCM 生命週期所述,Android 會停止支援較低版本的 HAL 介面。
  • 裝置上已安裝必要的 HAL,Android 必須使用某些 HAL 才能正常運作。

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

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

在 C++ 中,您可以使用 libaidlvintf_gtest_helper 中的 android::getAidlHalInstanceNames 函式,取得系統上安裝的所有 HAL 清單。在 Rust 中,請使用 binder::get_declared_instances

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

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

HAL 開發的 VTS 里程碑

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

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

在 Cuttlefish 上測試

如果沒有硬體,Android 會使用 Cuttlefish 做為 HAL 介面的開發工具。這項功能可讓您對 Android 進行可擴充的整合測試。

hal_implementation_test 測試 Cuttlefish 是否實作最新版 HAL 介面,確保 Android 已準備好處理新介面,且 VTS 測試已準備好測試新的供應商實作項目,以便在新的硬體和裝置推出後立即進行測試。