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 支援介面擁有者的就地版本管理:
    • 擁有者可以在介面結尾新增方法,或為 Parcelable 新增欄位。這表示多年來會更容易版本程式碼版本,而且每年的費用也較低 (可以就地修改,而且每個介面版本都不需要額外的程式庫)。
    • 擴充功能介面可在執行階段 (而非類型系統) 中附加,因此不需要將下游擴充功能重新建構到較新版本的介面上。
  • 當擁有者選擇穩定時,可以直接使用現有的 AIDL 介面。之前,介面的完整副本必須在 HIDL 中建立。

根據 AIDL 執行階段建構

AIDL 有三種不同的後端:Java、NDK、CPP。如要使用穩定版 AIDL,您必須一律在 system/lib*/libbinder.so 使用 libbinder 的系統副本,並透過 /dev/binder 對話。針對供應商映像檔上的程式碼,這表示無法使用來自 VNDK 的 libbinder:這個程式庫有不穩定的 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 的 Android 開放原始碼計畫穩定版 AIDL 介面與 HIDL 介面位於相同的基本目錄,位於 aidl 資料夾中。

  • 硬體/介面
  • 架構/硬體/介面
  • 系統/硬體/介面

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

擴充功能介面

每次發布時,Android 都會提供一組官方 AOSP 介面。如果 Android 合作夥伴想為這些介面新增功能,則不應直接變更這些介面,因為這代表他們的 Android 執行階段與 Android 開放原始碼計畫 Android 執行階段不相容。針對 GMS 裝置,避免變更這些介面也是確保 GSI 映像檔能繼續運作的原因。

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

不過,如果供應商專屬 (也就是上游 Android 開放原始碼計畫的一部分) 元件使用介面,但是已註冊擴充功能,系統就無法合併衝突。但是,當下游修改上游 Android 開放原始碼計畫元件時,可能會發生合併衝突,因此建議採用下列策略:

  • 下一版中,新增的介面可向上串流至 Android 開放原始碼計畫
  • 提升介面的彈性,而不用合併衝突

Extension parcelables:ParcelableHolder

ParcelableHolder 是可包含另一個 ParcelableParcelableParcelableHolder 的主要用途是將 Parcelable 設為可擴充。例如,裝置實作人員預期能夠擴充 Android 開放原始碼計畫定義的 ParcelableAospDefinedParcelable 來納入加值功能。

在先前沒有 ParcelableHolder 的情況下,裝置實作者無法修改 Android 開放原始碼計畫定義的穩定 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 中定義擴充點。

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 檔案建立 .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 屬性。否則,這項政策設定與任何其他 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 實際上不是詐騙物件。而是會由這些巨集用於參照與用戶端伺服器配對相關聯的屬性群組。

但是到目前為止,我們尚未將 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 的功能時,都必須使用附加的擴充功能。當需要全新的功能時,則不需要使用此機制,並且可以直接向服務管理員註冊擴充功能介面。附加的擴充功能介面最適合附加至子介面時最合理,因為這些階層可能為深度或多實體。使用全域擴充功能建立另一個服務的繫結器介面階層時,需要大量保留儲存空間,才能提供與直接附加擴充功能相同的功能。

如要在繫結器上設定擴充功能,請使用下列 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 後端和 Java 後端中使用 ServiceSpecificException,而不要建立自訂狀態類型。EX_SERVICE_SPECIFIC請參閱錯誤處理
  • 傳送繫結器物件時,AIDL 不會自動啟動執行緒集區。這些執行緒必須手動啟動 (請參閱執行緒管理)。
  • 在未勾選的傳輸錯誤時,AIDL 不會取消 (HIDL Return 會在未勾選的錯誤時取消)。
  • AIDL 每個檔案只能宣告一種類型。
  • AIDL 引數除了可指定為輸出參數之外,也可以指定為 in/out (沒有「同步回呼」)。
  • AIDL 使用 fd 做為原始類型,而非控制代碼。
  • HIDL 使用主要版本進行不相容的變更,並將次要版本用於相容變更。在 AIDL 中,系統會進行回溯相容的變更。AIDL 沒有主要版本的明確概念,而是會納入套件名稱中。例如,AIDL 可能會使用套件名稱 bluetooth2
  • 根據預設,AIDL 不會沿用即時優先順序。每個繫結器都必須使用 setInheritRt 函式,才能啟用即時優先順序繼承。