HAL 的 AIDL

Android 11 引進了在 Android 中對 HAL 使用 AIDL 的功能。這使得無需 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 HAL 接口

對於系統和供應商之間使用的 AIDL 接口,該接口需要進行兩處更改:

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

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

當您進行這些變更時,介面必須位於VINTF 清單中才能正常運作。使用 VTS 測試vts_treble_vintf_vendor_test對此進行測試(以及相關要求,例如驗證已發佈的介面是否已凍結)。您可以在沒有這些要求的情況下使用@VintfStability接口,方法是在發送 Binder 物件之前調用 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資料夾中。

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

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

擴充介面

Android 每個版本都有一套官方 AOSP 介面。當 Android 合作夥伴想要向這些接口添加功能時,他們不應該直接更改這些接口,因為這意味著他們的 Android 運行時與 AOSP Android 運行時不相容。對於 GMS 設備來說,避免更改這些介面也是確保 GSI 映像能夠繼續運作的原因。

擴充可以透過兩種不同的方式註冊:

然而,註冊擴充後,當特定於供應商(意味著不是上游 AOSP 的一部分)元件使用該介面時,就不存在合併衝突的可能性。然而,當下游對上游 AOSP 組件進行修改時,可能會導致合併衝突,建議採用以下策略:

  • 介面添加可以在下一版本中上游到 AOSP
  • 介面添加允許進一步的靈活性,沒有合併衝突,可以在下一個版本中上游化

擴充 Parcelables:ParcelableHolder

ParcelableHolder是一個Parcelable ,它可以包含另一個ParcelableParcelableHolder的主要用例是使Parcelable可擴展。例如,設備實現者期望能夠擴展 AOSP 定義的ParcelableAospDefinedParcelable ,以包含其增值功能。

以前如果沒有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中定義擴充點。

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 運行時構建

AIDL 有三種不同的後端:Java、NDK、CPP。要使用穩定的 AIDL,您必須始終使用位於system/lib*/libbinder.so的 libbinder 系統副本,並在/dev/binder上進行討論。對於供應商映像上的程式碼,這意味著無法使用libbinder (來自 VNDK):該程式庫具有不穩定的 C++ API 和不穩定的內部結構。相反,本機供應商程式碼必須使用 AIDL 的 NDK 後端,連結到libbinder_ndk (由系統libbinder.so支援),並連結到由aidl_interface條目建立的-ndk_platform庫。

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 介面參數不像 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 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 的功能時,就應使用附加擴充。當需要全新的功能時,不需要使用該機制,可以直接向服務管理員註冊擴充介面。附加擴充介面在附加到子介面時最有意義,因為這些層次結構可能很深或多實例。使用全域擴展來鏡像另一個服務的活頁夾介面層次結構將需要大量的簿記才能提供與直接附加的擴展相同的功能。

若要在活頁夾上設定擴展,請使用下列 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

您可以在對應後端的getExtension函數的文件中找到有關這些 API 的更多資訊。如何使用擴充功能的範例可以在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 。請參閱錯誤處理
  • 當發送binder物件時,AIDL不會自動啟動執行緒池。它們必須手動啟動(請參閱線程管理)。
  • AIDL 不會因未檢查的傳輸錯誤而中止(HIDL Return aborts on unchecked error)。
  • AIDL 每個檔案只能宣告一種類型。
  • 除了輸出參數之外,AIDL 參數還可以指定為 in/out/inout(沒有「同步回呼」)。
  • AIDL 使用 fd 作為原始類型而不是句柄。
  • HIDL 對不相容的變更使用主要版本,對相容的變更使用次要版本。在 AIDL 中,向後相容的變更已就地完成。 AIDL沒有明確的主要版本概念;相反,它被合併到套件名稱中。例如,AIDL 可能使用套件名稱bluetooth2
  • AIDL預設不繼承即時優先權。必須為每個綁定器使用setInheritRt函數才能啟用即時優先權繼承。