AIDL 後端

AIDL 後端是用於產生虛設程式碼。使用 AIDL 檔案時,請一律以特定執行階段搭配特定語言來使用這些檔案。視環境而定,您應使用不同的 AIDL 後端。

AIDL 的後端如下:

後端 語言 API 介面 建構系統
Java Java SDK/SystemApi (穩定版*) 全部
NDK C++ libbinder_ndk (穩定版*) aidl_interface
單一目標對象收視率點數費用 C++ libbinder (不穩定) 全部
Rust Rust libbinder_rs (不穩定) aidl_interface
  • 這些 API 介面相當穩定,但許多 API (例如服務管理 API ) 仍會保留供內部平台使用,而且不適用於應用程式。如要進一步瞭解如何在應用程式中使用 AIDL,請參閱開發人員說明文件
  • 「尖峰」後端是在 Android 12 中推出; NDK 後端已於 Android 10 推出。
  • 「籠子」建置於「libbinder_ndk」頂端。APEXes 使用的計算機方法與其他系統中的其他人相同。「尖峰」部分會包裝在 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_DL 模組的處理方式不同,系統不會將 AIDL 檔案指定為來源檔案。而是將aidl_interface模組會產生一個rustlib呼叫<aidl_interface name>-rust而且可以連結 詳情請參閱 Rust AIDL 範例

aidl_interface

請參閱 Stable AIDL。此建構系統使用的類型必須結構化;直接在 AIDL 中代表。也就是說,自訂樣式無法使用。

類型

您可以將 aidl 編譯器做為類型的參考實作。建立介面時,請叫用 aidl --lang=<backend> ... 以查看產生的介面檔案。使用 aidl_interface 模組時,您可以在 out/soong/.intermediates/<path to module>/ 中查看輸出。

Java/AIDL 類型 C++ 類型 NDK 類型 薄 Type 類型
布林值 bool bool bool
B int8_t int8_t I8
char 161 位數 161 位數 u16
int int32_t int32_t i32
偏長 int64_t int64_t i64
浮動值 浮動值 浮動值 F32
雙重 雙重 雙重 F64
字串 android::字串 16 std::字串 字串
android.os.Parcelable android::Parcelable
繫結 android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::向量<T> std::向量<T> 收件者:
T:Vec<T>
位元組 [] std::向量<uint8_t> std::jpg<int8_t>1 收件者:[u8]
外出:Vec<u8>
清單<T> std::向量<T>2 std::向量<T>3 收件者:[T]4
退場:Vec<T>
FileDescriptor android::base::unique_fd binder::parcel::ParcelFile 描述元
ParcelFile 描述元 android::os::ParcelFile 描述元 ndk::ScopedFile 描述元 binder::parcel::ParcelFile 描述元
介面類型 (T) android::sp<T> std::shared_ptr<T> binder::強
Parcelable 類型 (T) T T T
聯集類型 (T)5 T T T

1. 在 Android 12 以上版本中,位元組陣列會以 uint8_t 取代 tint8_t,而非基於相容性原因。

2. C++ 後端支援 List<T>,其中 TStringIBinderParcelFileDescriptor 或 Parcelable , 在 Android T (AOSP 實驗功能) 中,T 可以是任何非原始類型 (包括介面類型),但陣列除外。Android 開放原始碼計畫建議使用 T[] 等陣列類型,因為這些類型會在所有後端中運作。

3. NDK 後端支援 List<T>,其中 TStringParcelFileDescriptor 或 Parcelable 之一。在 Android T (AOSP 實驗功能) 以上中,T 可以是陣列以外的任何非原始類型。

4. 視輸入類型 (引數) 或輸出內容 (傳回的值) 而定,類型在 RAM 程式碼中的傳送方式會有所不同。

5. Android 12 以上版本支援聯合類型。

指向性 (傳入/關閉)

指定函式的引數類型時,您可以將引數指定為 inoutinout。這個控制項可控管 IPC 呼叫的方向資訊。in 是預設方向,表示資料會透過呼叫端傳送至來電者。out 表示資料是從呼叫端傳送至來電者。inout是兩者的組合, 不過,Android 團隊建議您不要避免使用引數指定器 inoutinout 對 Rust 而言,一般 inout 類型會接收 &mut Vec<T>,而 inout 清單接收 &mut Vec<T>

UTF8/UTF16

C++ 後端可讓您選擇字串為 utf-8 或 utf-16。在 AIDL 中將字串宣告為 @utf8InCpp String,自動將字串轉換為 utf-8。NDK 和 Rust 後端一律使用 utf-8 字串。如要進一步瞭解 utf8InCpp 註解,請參閱 AIDL 中的註解

是否可以為空值

您可以在 Java 中,為@nullable為空值和 NDK 顯示空值。過去,@nullable 類型會顯示為 Option<T>。根據預設,原生伺服器拒絕空值。唯一的例外是 interfaceIBinder 類型,一律為空值。如要進一步瞭解 nullable 註解,請參閱 AIDL 中的註解

自訂 Parcelable

在核心建構系統的 C++ 和 Java 後端中,您可以宣告在目標後端 (C++ 或 Java) 中手動實作的用戶端版本。

    package my.package;
    parcelable Foo;

或是 C++ 標頭宣告:

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

然後,您便可以使用這個視同類型來當做 AIDL 檔案的類型,但不會由 AIDL 產生。

「Rust」不支援自訂 Parcelable。

預設值

結構化的可剖析項目可針對這些類型的原始欄位、String 和陣列,宣告每個欄位的預設值。

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

在缺少預設值時,Java 後端中的欄位欄位會初始化為原始類型和零值,並針對非原始類型使用 null

在其他後端中,如未定義預設值,系統會使用預設初始化值來初始化欄位。舉例來說,在 C++ 後端中,String 欄位會以空白字串初始化,而 List<T> 欄位會初始化為空白的 vector<T>@nullable 欄位的初始化欄位為空值。

處理錯誤

Android 作業系統提供內建錯誤類型,供服務錯誤回報時使用。兩者可用於二進位檔 (任何實作器介面)。合作夥伴在 AIDL 定義中已妥善運用,而且不需要任何使用者定義的狀態或傳回類型。

如果 AIDL 介面需要未包含在內建錯誤類型中的其他錯誤值,則可以使用特殊服務的內建錯誤,以便納入服務專屬的錯誤使用者定義的值。這類服務專屬的錯誤一般是透過 AIDL 介面定義為 const intint 支援的 enum,且剖析器不會剖析。

在 Java 中,錯誤會對應至例外狀況,例如 android.os.RemoteException。針對服務專屬例外狀況,Java 使用 android.os.ServiceSpecificException 和使用者定義的錯誤。

Android 中的原生程式碼不會有例外狀況。CPP 後端會使用 android::binder::Status。NDK 後端使用 ndk::ScopedAStatus。AIDL 產生的每個方法都會傳回其中一種方法,表示方法狀態。「尖峰」後端使用與 NDK 相同的例外狀況代碼值,但會先將其轉換成原生的「RR 錯誤」(StatusCodeExceptionCode),然後再向使用者放送。如果是服務相關的錯誤,傳回的 StatusScopedAStatus 會使用 EX_SERVICE_SPECIFIC 和使用者定義的錯誤。

內建檔案如下:內建錯誤類型:

後端 定義
Java android/os/Parcel.java
單一目標對象收視率點數費用 binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

使用多個後端

以下操作說明僅適用於 Android 平台程式碼。這些範例使用定義的類型 my.package.IFoo, 如需 Rust 後端的使用操作說明,請參閱 Android 尖峰模式頁面上的 Rust AIDL 範例

匯入類型

無論您定義的類型是介面、可剖析或聯集類型,您都可以在 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 後端中,則必須加入其根類型的標頭。舉例來說,匯入巢狀類型時Bar定義my/package/IFoo.aidl (IFoo必須納入的檔案根類型)<my/package/IFoo.h> CPP 後端 (或 <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 也會檢查服務 (也就是說,如果服務無法使用,則應立即傳回 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()
}

您可以要求在託管受阻者服務終止時收到通知。 這有助於避免回呼回呼 Proxy 或因錯誤復原而有所幫助。 並在二進位 Proxy 物件上進行呼叫。

  • 在 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 服務,Error Reporting 會使用所有已註冊服務管理員的服務使用的二進位檔 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) {...}

在 C++ 後端中:

    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<()>

動態取得介面描述元

介面描述元會識別介面類型。這個偵錯方法適用於偵錯作業或不明的 bin 這項作業。

在 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()
````

### Thread management {:#thread-management}

Every instance of `libbinder` in a process maintains one threadpool. For most
use cases, this should be exactly one threadpool, shared across all backends. The
only exception to this is when vendor code might load another copy of `libbinder`
to talk to `/dev/vndbinder`.  Since this is on a separate binder node, the
threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (since it is
already started):

BinderInternal.setMaxThreads(<new larger value>);

For the C++ backend, the following operations are available:

// 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();

Similarly, in the NDK backend:

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

In the Rust backend:

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