AIDL 後端

AIDL 後端是存根程式碼生成的目標。請務必在特定語言中使用 AIDL 檔案,並搭配特定執行階段。視情況而定,您應使用不同的 AIDL 後端。

下表中的 API 介面穩定性是指針對這個 API 介面編譯程式碼的能力,這樣程式碼就能獨立於 system.img libbinder.so 二進位檔交付。

AIDL 具有下列後端:

後端 語言 API 介面 建構系統
Java Java SDK 或 SystemApi (穩定版*) 全部
NDK C++ libbinder_ndk (穩定版*) aidl_interface
CPP C++ libbinder (不穩定) 全部
Rust Rust libbinder_rs (穩定版*) aidl_interface
  • 這些 API 介面穩定,但許多 API (例如服務管理 API) 僅供內部平台使用,不開放應用程式存取。如要進一步瞭解如何在應用程式中使用 AIDL,請參閱「Android 介面定義語言 (AIDL)」。
  • Android 12 推出 Rust 後端,Android 10 則推出 NDK 後端。
  • Rust Crate 是以 libbinder_ndk 為基礎建構,因此穩定且可攜。APEX 會以標準方式在系統端使用繫結 Crate。Rust 部分會併入 APEX,並在其中運送。這部分取決於系統磁碟分割區上的 libbinder_ndk.so

建構系統

視後端而定,將 AIDL 編譯為 Stub 程式碼的方法有兩種。如要進一步瞭解建構系統,請參閱「Soong 模組參考資料」。

核心建構系統

在任何 cc_java_ Android.bp module (或其 Android.mk 等效項目) 中,您可以將 AIDL (.aidl) 檔案指定為來源檔案。在本例中,系統會使用 AIDL 的 Java 或 CPP 後端 (而非 NDK 後端),並自動將使用對應 AIDL 檔案的類別新增至模組。您可以在這些模組的 aidl: 群組下指定選項,例如 local_include_dirs (告知建構系統該模組中 AIDL 檔案的根路徑)。

Rust 後端僅適用於 Rust。rust_ 模組的處理方式不同,因為 AIDL 檔案不會指定為原始檔。aidl_interface 模組會產生名為 aidl_interface_name-rustrustlib,可供連結。詳情請參閱 Rust AIDL 範例

aidl_interface

搭配 aidl_interface 建構系統使用的型別必須為結構化型別。如要結構化,可 Parcelable 必須直接包含欄位,且不能是目標語言中直接定義的型別宣告。如要瞭解結構化 AIDL 如何與穩定 AIDL 搭配使用,請參閱「結構化與穩定 AIDL 比較」。

類型

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

Java 或 AIDL 型別 C++ 型別 NDK 類型 Rust 類型
boolean bool bool bool
byte8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string 輸入:&str
輸出:String
android.os.Parcelable android::Parcelable
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> 輸入:&[T]
輸出:Vec<T>
byte[] std::vector std::vector1 輸入:&[u8]
輸出:Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 In: In: &[T]4
Out: Vec<T>
FileDescriptor android::base::unique_fd
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
介面類型 (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
可封送類型 (T) T T T
聯集型別 (T)5 T T T
T[N]6 std::array<T, N> std::array<T, N> [T; N]

1. 在 Android 12 以上版本中,位元組陣列會使用 uint8_t,而非 int8_t,以確保相容性。

2. C++ 後端支援 List<T>,其中 TStringIBinderParcelFileDescriptor 或可封送的其中一個。在 Android 13 以上版本中,T 可以是任何非原始型別 (包括介面型別),但陣列除外。AOSP 建議使用 T[] 等陣列型別,因為這些型別適用於所有後端。

3. NDK 後端支援 List<T>,其中 TStringParcelFileDescriptor 或可封送的項目。在 Android 13 以上版本中,T 可以是陣列以外的任何非原始型別。

4. 視型別是輸入內容 (引數) 或輸出內容 (傳回的值) 而定,Rust 程式碼的型別傳遞方式有所不同。

5. Android 12 以上版本支援聯集型別。

6. Android 13 以上版本支援固定大小的陣列。固定大小的陣列可以有多個維度 (例如 int[3][4])。在 Java 後端,固定大小的陣列會以陣列型別表示。

7. 如要建立繫結器 SharedRefBase 物件的例項,請使用 SharedRefBase::make\<My\>(... args ...)。如果繫結器是由其他程序擁有,這個函式會建立 std::shared_ptr\<T\> 物件,該物件也會在內部管理。以其他方式建立物件會導致雙重擁有權。

8. 另請參閱 Java 或 AIDL 型別 byte[]

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

指定函式引數的型別時,您可以將引數指定為 inoutinout。這項設定可控管 IPC 呼叫傳遞資訊的方向。

  • in 引數指定符表示資料會從呼叫端傳遞至被呼叫端。in 規範是預設方向,但如果資料類型也可以是 out,則必須指定方向。

  • out 引數指定符表示資料會從被呼叫端傳遞至呼叫端。

  • inout 引數指定符是這兩者的組合。不過,建議您避免使用引數指定符 inout。如果您搭配使用 inout 與版本化介面和舊版呼叫端,則只有呼叫端中存在的額外欄位會重設為預設值。就 Rust 而言,一般 inout 型別會接收 &mut T,而清單 inout 型別會接收 &mut Vec<T>

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 和 UTF-16

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

是否可為空值

您可以為可為空值的型別加上 @nullable 註解。 如要進一步瞭解 nullable 註解,請參閱 nullable

自訂 Parcelable

自訂可封送物件是在目標後端手動實作的可封送物件。只有在嘗試為無法變更的現有自訂可封裝項目新增其他語言的支援時,才使用自訂可封裝項目。

以下是 AIDL 可封送物件宣告的範例:

    package my.pack.age;
    parcelable Foo;

根據預設,這會宣告 Java 可封送物件,其中 my.pack.age.Foo 是實作 Parcelable 介面的 Java 類別。

如要在 AIDL 中宣告自訂 CPP 後端可 Parcelable,請使用 cpp_header

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

my/pack/age/Foo.h 中的 C++ 實作方式如下:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

如要在 AIDL 中宣告自訂 NDK 可封送物件,請使用 ndk_header

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

android/pack/age/Foo.h 中的 NDK 實作方式如下:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

在 Android 15 中,如要在 AIDL 中宣告自訂 Rust 可封送物件,請使用 rust_type

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

rust_crate/src/lib.rs 中的 Rust 實作看起來會像這樣:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

接著,您可以在 AIDL 檔案中將這個可封送物件做為型別,但 AIDL 不會產生這個物件。為 CPP 和 NDK 後端自訂可打包物件提供 <== 運算子,以便在 union 中使用。

預設值

結構化可 Parcelable 物件可為原始型別、String 欄位和這些型別的陣列宣告每個欄位的預設值。

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

在 Java 後端,如果缺少預設值,系統會將欄位初始化為原始型別的零值,以及非原始型別的 null

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

聯集

AIDL 聯集會加上標記,且所有後端的特徵都類似。這些建構函式會建構至第一個欄位的預設值,且具有語言專屬的互動方式:

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Java 範例

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

C++ 和 NDK 範例

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Rust 範例

在 Rust 中,聯集會實作為列舉,且沒有明確的 Getter 和 Setter。

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

處理錯誤

Android OS 提供內建錯誤類型,供服務回報錯誤時使用。這些是繫結器使用的項目,任何實作繫結器介面的服務都可以使用。AIDL 定義中已詳細說明其用途,且不需要任何使用者定義的狀態或傳回型別。

輸出參數有誤

如果 AIDL 函式回報錯誤,函式可能不會初始化或修改輸出參數。具體來說,如果錯誤是在解除封裝期間發生,而非在處理交易本身時發生,輸出參數可能會遭到修改。一般來說,如果從 AIDL 函式取得錯誤,所有 inoutout 參數以及傳回值 (在某些後端中,傳回值的作用類似 out 參數) 都應視為處於不確定狀態。

要使用的錯誤值

許多內建錯誤值都可用於任何 AIDL 介面,但部分錯誤值的處理方式較為特殊。舉例來說,EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT 可用來描述錯誤情況,但 EX_TRANSACTION_FAILED 不得使用,因為基礎架構會特別處理這個值。如要進一步瞭解這些內建值,請查看後端專屬定義。

如果 AIDL 介面需要內建錯誤類型未涵蓋的其他錯誤值,可以使用特殊服務專屬內建錯誤,納入使用者定義的服務專屬錯誤值。這些服務專屬錯誤通常會在 AIDL 介面中定義為 const intint 支援的 enum,且不會由繫結器剖析。

在 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,以及使用者定義的錯誤。

內建錯誤類型位於下列檔案中:

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

使用各種後端

以下操作說明適用於 Android 平台程式碼。這些範例使用定義的型別 my.package.IFoo。如要瞭解如何使用 Rust 後端,請參閱 Android Rust 模式中的 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 後端,您必須納入根型別的標頭。舉例來說,匯入 my/package/IFoo.aidl 中定義的巢狀型別 Bar 時 (IFoo 是檔案的根型別),您必須為 CPP 後端加入 <my/package/IFoo.h> (或為 NDK 後端加入 <aidl/my/package/IFoo.h>)。

實作介面

如要實作介面,您必須從原生虛設常式類別繼承。介面的實作項目通常會向服務管理員或 android.app.ActivityManager 註冊,並稱為「服務」;如果是由服務的用戶端註冊,則稱為「回呼」。不過,視確切用途而定,介面實作的名稱可能有所不同。存根類別會從繫結驅動程式讀取指令,並執行您實作的方法。假設您有如下所示的 AIDL 檔案:

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

在 Java 中,您必須從產生的 Stub 類別擴充:

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

或使用非同步 Rust:

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

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

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

註冊並取得服務

平台 Android 中的服務通常會向 servicemanager 程序註冊。除了下列 API 之外,部分 API 會檢查服務 (也就是說,如果服務無法使用,API 會立即傳回結果)。如要瞭解確切詳細資料,請查看對應的 servicemanager 介面。只有在針對 Android 平台進行編譯時,才能執行這些作業。

在 Java 中:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("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);
    // return if service is started now
    status_t err = checkService<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
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::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()
}

在非同步 Rust 後端,使用單一執行緒執行階段:

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

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks run on this thread.
    std::future::pending().await
}

與其他選項的重要差異在於,使用非同步 Rust 和單一執行緒執行階段時,不會呼叫 join_thread_pool。這是因為您需要提供 Tokio 執行衍生工作的執行緒。在下列範例中,主執行緒就是用於此目的。使用 tokio::spawn 產生的任何工作都會在主執行緒上執行。

在非同步 Rust 後端,使用多執行緒執行階段:

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

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

使用多執行緒 Tokio 執行階段時,產生的工作不會在主執行緒上執行。因此,在主執行緒上呼叫 join_thread_pool 會比較合理,這樣主執行緒就不會閒置。您必須在呼叫前後加上 block_in_place,才能離開非同步環境。

您可以要求在裝載繫結器的服務終止時收到通知。 這有助於避免回呼 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 會提供呼叫端資訊。程序 ID (PID) 是指傳送交易的程序的 Linux 程序 ID。使用者 ID (UI) 是指 Linux 使用者 ID。收到單向通話時,呼叫 PID 為 0。 在繫結器交易情境之外,這些函式會傳回目前程序的 PID 和 UID。

在 Java 後端:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

在 CPP 後端:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

在 NDK 後端:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

在 Rust 後端實作介面時,請指定下列項目 (而非允許預設):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

服務的錯誤報告和偵錯 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 支援 WeakReference,但原生層不支援弱繫結器參照。

在 CPP 後端,弱型別為 wp<IFoo>

在 NDK 後端,請使用 ScopedAIBinder_Weak

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

在 Rust 後端,使用 WpIBinderWeak<IFoo>

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

動態取得介面描述元

介面描述元會識別介面的類型。這在偵錯或有不明繫結器時非常實用。

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

執行緒管理

程序中的每個 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();

使用非同步 Rust 後端時,您需要兩個執行緒集區:繫結器和 Tokio。也就是說,使用非同步 Rust 的應用程式需要特別注意,尤其是使用 join_thread_pool 時。詳情請參閱服務註冊一節

保留名稱

C++、Java 和 Rust 會保留部分名稱做為關鍵字,或供語言專用。雖然 AIDL 不會根據語言規則強制執行限制,但使用與保留名稱相符的欄位或型別名稱,可能會導致 C++ 或 Java 編譯失敗。如果是 Rust,系統會使用原始 ID 語法重新命名欄位或型別,並透過 r# 前置字串存取。

建議您盡可能避免在 AIDL 定義中使用保留名稱,以免產生不符合人體工學的繫結,或直接導致編譯失敗。

如果 AIDL 定義中已有保留名稱,您可以安全地重新命名欄位,同時維持通訊協定相容性。您可能需要更新程式碼才能繼續建構,但已建構的程式仍可互通。

應避免的名稱: