AIDL 後端

AIDL 後端是存根代碼生成的目標。使用 AIDL 文件時,您始終以具有特定運行時的特定語言使用它們。根據上下文,您應該使用不同的 AIDL 後端。

AIDL 有以下後端:

後端語言API 表面構建系統
爪哇爪哇SDK/SystemApi (穩定*)全部
NDK C++ libbinder_ndk(穩定*) aidl_interface
CPP C++ libbinder(不穩定)全部
libbinder_rs(不穩定) aidl_interface
  • 這些 API 表面是穩定的,但許多 API(例如用於服務管理的 API)是為內部平台使用而保留的,對應用程序不可用。有關如何在應用程序中使用 AIDL 的更多信息,請參閱開發者文檔
  • Rust 後端是在 Android 12 中引入的; NDK 後端從 Android 10 開始可用。
  • Rust crate 建立在libbinder_ndk 。 APEX 使用 binder crate 的方式與系統端的其他任何人相同。 Rust 部分被捆綁到 APEX 中並在其中運送。它取決於系統分區上的libbinder_ndk.so

構建系統

根據後端的不同,有兩種方法可以將 AIDL 編譯為存根代碼。有關構建系統的更多詳細信息,請參閱Soong 模塊參考

核心構建系統

在任何cc_java_ Android.bp 模塊(或它們的Android.mk等效模塊)中,可以將.aidl文件指定為源文件。在這種情況下,使用 AIDL 的 Java/CPP 後端(不是 NDK 後端),並自動將使用相應 AIDL 文件的類添加到模塊中。諸如local_include_dirs之類的選項告訴構建系統該模塊中 AIDL 文件的根路徑可以在這些模塊中的aidl:組中指定。請注意,Rust 後端僅用於 Rust。 rust_模塊的處理方式不同,因為 AIDL 文件未指定為源文件。相反, aidl_interface模塊會生成一個名為<aidl_interface name>-rust rustlib rustlib,它可以被鏈接。有關更多詳細信息,請參閱Rust AIDL 示例

aidl_interface

請參閱穩定的 AIDL 。與此構建系統一起使用的類型必須是結構化的;即直接用 AIDL 表示。這意味著不能使用自定義 Parcelables。

類型

您可以將aidl編譯器視為類型的參考實現。創建接口時,調用aidl --lang=<backend> ...查看生成的接口文件。當您使用aidl_interface模塊時,您可以在out/soong/.intermediates/<path to module>/中查看輸出。

Java/AIDL 類型C++ 類型NDK 類型銹型
布爾值布爾布爾布爾
字節int8_t int8_t i8
字符char16_t char16_t u16
整數int32_t int32_t i32
int64_t int64_t i64
漂浮漂浮漂浮f32
雙倍的雙倍的雙倍的f64
細繩安卓::String16標準::字符串細繩
android.os.Parcelable android::Parcelable不適用不適用
綁定器安卓::IBinder ndk::SpAIBinder活頁夾::SpIBinder
T[]標準::向量<T>標準::向量<T>在:&T
輸出: Vec<T>
字節[] std::vector<uint8_t> std::vector<int8_t> 1在:&[u8]
輸出:Vec<u8>
列表<T>標準::向量<T> 2標準::向量<T> 3在:&[T] 4
輸出: Vec<T>
文件描述符android::base::unique_fd不適用binder::parcel::ParcelFileDescriptor
包裹文件描述符android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
接口類型 (T)安卓::sp<T> std::shared_ptr<T>活頁夾::強
可包裹類型 (T)
聯合類型 (T) 5
T[N] 6標準::數組<T, N>標準::數組<T, N> [T; N]

1. 在 Android 12 或更高版本中,出於兼容性原因,字節數組使用 uint8_t 而不是 int8_t。

2. C++ 後端支持List<T>其中TStringIBinderParcelFileDescriptor或 parcelable 之一。在 Android T(AOSP 實驗性)或更高版本中, T可以是除數組之外的任何非原始類型(包括接口類型)。 AOSP 建議您使用像T[]這樣的數組類型,因為它們適用於所有後端。

3. NDK 後端支持List<T> ,其中TStringParcelFileDescriptor或 parcelable 之一。在 Android T(AOSP 實驗性)或更高版本中, T可以是除數組之外的任何非原始類型。

4. Rust 代碼的類型傳遞不同,具體取決於它們是輸入(參數)還是輸出(返回值)。

5. Android 12 及更高版本支持聯合類型。

6. 在Android T(AOSP 實驗版)或更高版本中,支持固定大小的數組。固定大小的數組可以有多個維度(例如int[3][4] )。在 Java 後端,固定大小的數組表示為數組類型。

方向性(輸入/輸出/輸入)

指定函數參數的類型時,可以將它們指定為inoutinout 。這控制了為 IPC 調用傳遞方向信息的方向。 in是默認方向,表示數據從調用者傳遞到被調用者。 out表示數據從被調用者傳遞給調用者。 inout是這兩者的結合。但是,Android 團隊建議您避免使用參數說明符inout 。如果您將inout與版本化接口和較舊的被調用者一起使用,則僅存在於調用者中的附加字段將重置為其默認值。對於 Rust,普通的inout類型接收&mut Vec<T> ,而 list inout類型接收&mut Vec<T>

UTF8/UTF16

使用 CPP 後端,您可以選擇字符串是 utf-8 還是 utf-16。在 AIDL 中將字符串聲明為@utf8InCpp String以自動將它們轉換為 utf-8。 NDK 和 Rust 後端總是使用 utf-8 字符串。有關utf8InCpp註釋的更多信息,請參閱AIDL 中的註釋

可空性

您可以使用@nullable註釋 Java 後端中可以為空的類型,以將空值公開給 CPP 和 NDK 後端。在 Rust 後端,這些@nullable類型公開為Option<T> 。默認情況下,本機服務器拒絕空值。唯一的例外是interfaceIBinder類型,對於 NDK 讀取和 CPP/NDK 寫入,它們始終可以為空。有關nullable註釋的更多信息,請參閱AIDL 中的註釋

自定義 Parcelables

在核心構建系統的 C++ 和 Java 後端,您可以聲明在目標後端(C++ 或 Java)中手動實現的 Parcelable。

    package my.package;
    parcelable Foo;

或使用 C++ 標頭聲明:

    package my.package;
    parcelable Foo cpp_header "my/package/Foo.h";

然後你可以在 AIDL 文件中使用這個 parcelable 作為類型,但它不會由 AIDL 生成。

Rust 不支持自定義 parcelables。

默認值

結構化 Parcelable 可以為這些類型的基元、 String和數組聲明每個字段的默認值。

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

在 Java 後端中,當缺少默認值時,字段被初始化為原始類型的零值和非原始類型的null

在其他後端,當未定義默認值時,使用默認初始化值初始化字段。例如,在 C++ 後端, String字段初始化為空字符串, List<T>字段初始化為空vector<T>@nullable字段被初始化為空值字段。

錯誤處理

Android 操作系統提供了內置的錯誤類型供服務在報告錯誤時使用。這些由 binder 使用,並且可以由實現 binder 接口的任何服務使用。它們的使用在 AIDL 定義中有詳細記錄,並且不需要任何用戶定義的狀態或返回類型。

當 AIDL 函數報告錯誤時,該函數可能不會初始化或修改輸出參數。具體來說,如果錯誤發生在拆包期間,而不是發生在交易本身的處理期間,則可以修改輸出參數。一般來說,當從 AIDL 函數中得到錯誤時,所有的inoutout參數以及返回值(在某些後端就像一個out參數)都應該被認為處於不確定狀態。

如果 AIDL 接口需要內置錯誤類型未涵蓋的其他錯誤值,則它們可以使用特殊的特定於服務的內置錯誤,該錯誤允許包含由用戶定義的特定於服務的錯誤值.這些特定於服務的錯誤通常在 AIDL 接口中定義為const intint支持的enum ,並且不被 binder 解析。

在 Java 中,錯誤映射到異常,例如android.os.RemoteException 。對於特定於服務的異常,Java 使用android.os.ServiceSpecificException以及用戶定義的錯誤。

Android 中的本機代碼不使用異常。 CPP 後端使用android::binder::Status 。 NDK 後端使用ndk::ScopedAStatus 。 AIDL 生成的每個方法都返回其中之一,表示方法的狀態。 Rust 後端使用與 NDK 相同的異常代碼值,但在將它們傳遞給用戶之前將它們轉換為原生 Rust 錯誤( StatusCodeExceptionCode )。對於特定於服務的錯誤,返回的StatusScopedAStatus使用EX_SERVICE_SPECIFIC以及用戶定義的錯誤。

內置錯誤類型可以在以下文件中找到:

後端定義
爪哇android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
android/binder_status.h

使用各種後端

這些說明特定於 Android 平台代碼。這些示例使用定義的類型my.package.IFoo 。有關如何使用 Rust 後端的說明,請參閱Android Rust 模式頁面上的Rust AIDL 示例

導入類型

無論定義的類型是接口、parcelable 還是聯合,都可以在 Java 中導入:

    import my.package.IFoo;

或者在 CPP 後端:

    #include <my/package/IFoo.h>

或者在 NDK 後端(注意額外的aidl命名空間):

    #include <aidl/my/package/IFoo.h>

或者在 Rust 後端:

    use my_package::aidl::my::package::IFoo;

儘管您可以在 Java 中導入嵌套類型,但在 CPP/NDK 後端中,您必須包含其根類型的標頭。例如,當導入在my/package/IFoo.aidl中定義的嵌套類型BarIFoo是文件的根類型)時,您必須為 CPP 後端包含<my/package/IFoo.h> (或<aidl/my/package/IFoo.h>用於 NDK 後端)。

實施服務

要實現服務,您必須從本機存根類繼承。此類從活頁夾驅動程序讀取命令並執行您實現的方法。想像一下,你有一個這樣的 AIDL 文件:

    package my.package;
    interface IFoo {
        int doFoo();
    }

在 Java 中,您必須從此類擴展:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

在 CPP 後端:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

在 NDK 後端(注意額外的aidl命名空間):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

在 Rust 後端:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

註冊和獲取服務

Android 平台中的服務通常註冊到servicemanager進程中。除了下面的 API 之外,一些 API 還會檢查服務(這意味著如果服務不可用,它們會立即返回)。檢查相應的servicemanager接口以獲取確切的詳細信息。這些操作只能在針對 Android 平台編譯時進行。

在 Java 中:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

在 CPP 後端:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

在 NDK 後端(注意額外的aidl命名空間):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

在 Rust 後端:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

您可以請求獲取有關託管活頁夾的服務何時終止的通知。這可以幫助避免洩漏回調代理或幫助錯誤恢復。在活頁夾代理對像上進行這些調用。

  • 在 Java 中,使用android.os.IBinder::linkToDeath
  • 在 CPP 後端,使用android::IBinder::linkToDeath
  • 在 NDK 後端,使用AIBinder_linkToDeath
  • 在 Rust 後端,創建一個DeathRecipient對象,然後調用my_binder.link_to_death(&mut my_death_recipient) 。請注意,因為DeathRecipient擁有回調,所以只要您想接收通知,就必須保持該對象處於活動狀態。

服務的錯誤報告和調試 API

當錯誤報告運行時(例如,使用adb bugreport ),它們會從系統周圍收集信息以幫助調試各種問題。對於 AIDL 服務,錯誤報告使用服務管理器註冊的所有服務上的二進制dumpsys將其信息轉儲到錯誤報告中。您還可以在命令行上使用dumpsys從帶有dumpsys SERVICE [ARGS]的服務中獲取信息。在 C++ 和 Java 後端,您可以通過使用addService的附加參數來控制服務轉儲的順序。您還可以在調試時使用dumpsys --pid SERVICE來獲取服務的 PID。

要將自定義輸出添加到您的服務,您可以覆蓋服務器對像中的dump方法,就像您實現 AIDL 文件中定義的任何其他 IPC 方法一樣。執行此操作時,您應該限制轉儲到應用程序權限android.permission.DUMP或限制轉儲到特定 UID。

在 Java 後端:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

在 CPP 後端:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

在 NDK 後端:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

在 Rust 後端,當實現接口時,指定以下內容(而不是允許它默認):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

動態獲取接口描述符

接口描述符標識接口的類型。這在調試或有未知綁定器時很有用。

在 Java 中,您可以使用以下代碼獲取接口描述符:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

在 CPP 後端:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

NDK 和 Rust 後端不支持此功能。

靜態獲取接口描述符

有時(比如註冊@VintfStability服務時),你需要知道靜態的接口描述符是什麼。在 Java 中,您可以通過添加以下代碼來獲取描述符:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

在 CPP 後端:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

在 NDK 後端(注意額外的aidl命名空間):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

在 Rust 後端:

    aidl::my::package::BnFoo::get_descriptor()

枚舉範圍

在本機後端,您可以迭代枚舉可以採用的可能值。由於代碼大小的考慮,Java 目前不支持此功能。

對於 AIDL 中定義的枚舉MyEnum ,迭代提供如下。

在 CPP 後端:

    ::android::enum_range<MyEnum>()

在 NDK 後端:

   ::ndk::enum_range<MyEnum>()

在 Rust 後端:

    MyEnum::enum_range()

線程管理

進程中的每個libbinder實例都維護一個線程池。對於大多數用例,這應該是一個線程池,在所有後端共享。唯一的例外是供應商代碼可能會加載另一個libbinder副本以與/dev/vndbinder 。由於這是在單獨的綁定器節點上,因此線程池不共享。

對於 Java 後端,線程池的大小只能增加(因為它已經啟動):

    BinderInternal.setMaxThreads(<new larger value>);

對於 CPP 後端,可以使用以下操作:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

同樣,在 NDK 後端:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

在 Rust 後端:

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();