Backend AIDL

Backend AIDL adalah target untuk pembuatan kode stub. Saat menggunakan file AIDL, Anda selalu menggunakannya dalam bahasa tertentu dengan runtime tertentu. Bergantung pada konteks, Anda harus menggunakan backend AIDL yang berbeda.

Dalam tabel berikut, stabilitas platform API mengacu pada kemampuan untuk mengompilasi kode terhadap platform API ini dengan cara agar kode dapat dikirim secara independen dari biner system.img libbinder.so.

AIDL memiliki backend berikut:

{i>Backend<i} Bahasa Platform API Sistem build
Java Java SDK/SystemApi (stabil*) semua
NDK C++ libbinder_ndk (stabil*) aidl_interface
CPP C++ libbinder (tidak stabil) semua
Rust Rust libbinder_rs (stabil*) aidl_interface
  • Platform API ini stabil, tetapi banyak API, seperti API untuk pengelolaan layanan, dikhususkan untuk penggunaan platform internal dan tidak tersedia untuk aplikasi. Untuk mengetahui informasi selengkapnya tentang cara menggunakan AIDL dalam aplikasi, lihat dokumentasi developer.
  • Backend Rust diperkenalkan di Android 12; backend NDK telah tersedia mulai Android 10.
  • Peti Rust dibuat di atas libbinder_ndk, yang memungkinkannya menjadi stabil dan portabel. APEX menggunakan binder crate dengan cara yang sama seperti yang dilakukan pengguna lain pada sisi sistem. Bagian Rust dipaketkan ke dalam APEX dan dikirim di dalamnya. Hal ini bergantung pada libbinder_ndk.so pada partisi sistem.

Sistem build

Bergantung pada backend, ada dua cara untuk mengompilasi AIDL menjadi kode stub. Untuk detail selengkapnya tentang sistem build, lihat Referensi Modul Soong.

Sistem build inti

Dalam modul Android.bp cc_ atau java_ (atau dalam Android.mk yang setara), file .aidl dapat ditentukan sebagai file sumber. Dalam hal ini, backend Java/CPP AIDL digunakan (bukan backend NDK), dan class untuk menggunakan file AIDL yang sesuai akan otomatis ditambahkan ke modul. Opsi seperti local_include_dirs, yang memberi tahu sistem build jalur root ke file AIDL dalam modul tersebut dapat ditentukan dalam modul ini dalam grup aidl:. Perhatikan bahwa backend Rust hanya untuk digunakan dengan Rust. Modul rust_ ditangani secara berbeda sehingga file AIDL tidak ditentukan sebagai file sumber. Sebagai gantinya, modul aidl_interface menghasilkan rustlib yang disebut <aidl_interface name>-rust yang dapat ditautkan. Untuk mengetahui detail selengkapnya, lihat contoh AIDL Rust.

aidl_interface

Jenis yang digunakan dengan sistem build ini harus berstruktur. Agar terstruktur, parcelable harus berisi kolom secara langsung dan bukan deklarasi jenis yang ditentukan secara langsung dalam bahasa target. Untuk mengetahui kesesuaian AIDL terstruktur dengan AIDL stabil, lihat AIDL terstruktur versus stabil.

Jenis

Anda dapat menganggap compiler aidl sebagai implementasi referensi untuk jenis. Saat Anda membuat antarmuka, panggil aidl --lang=<backend> ... untuk melihat file antarmuka yang dihasilkan. Saat menggunakan modul aidl_interface, Anda dapat melihat outputnya dalam out/soong/.intermediates/<path to module>/.

Jenis Java/AIDL Jenis C++ Jenis NDK Jenis Karat
boolean bool bool Bool
byte8 int8_t int8_t i8
karakter char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
ganda ganda ganda F64
String android::String16 std::string String
android.os.Parcelable android::Parcelable T/A T/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
K[] std::vektor<T> std::vector<T> Masuk: &[T]
Keluar: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 Masuk: &[u8]
Keluar: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 Masuk: &[T]4
Keluar: Vec<T>
FileDescriptor android::base::unique_fd T/A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
jenis antarmuka (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
jenis parcelable (T) T T T
jenis union (T)5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; T]

1. Di Android 12 atau yang lebih tinggi, array byte menggunakan uint8_t, bukan int8_t, karena alasan kompatibilitas.

2. Backend C++ mendukung List<T> dengan T adalah salah satu dari String, IBinder, ParcelFileDescriptor, atau parcelable. Di Android 13 atau yang lebih baru, T dapat berupa jenis non-primitif (termasuk jenis antarmuka) kecuali array. AOSP merekomendasikan agar Anda menggunakan jenis array seperti T[], karena jenis ini berfungsi di semua backend.

3. Backend NDK mendukung List<T> dengan T adalah salah satu dari String, ParcelFileDescriptor, atau parcelable. Di Android 13 atau yang lebih tinggi, T dapat berupa jenis non-primitif kecuali array.

4. Jenis diteruskan secara berbeda untuk kode Rust, bergantung pada apakah jenis tersebut merupakan input (argumen), atau output (nilai yang ditampilkan).

5. Jenis union didukung di Android 12 dan yang lebih tinggi.

6. Di Android 13 atau yang lebih tinggi, array berukuran tetap didukung. Array berukuran tetap dapat memiliki beberapa dimensi (misalnya, int[3][4]). Di backend Java, array berukuran tetap direpresentasikan sebagai jenis array.

7. Untuk membuat instance objek SharedRefBase binder, gunakan SharedRefBase::make\<My\>(... args ...). Fungsi ini membuat objek std::shared_ptr\<T\> yang juga dikelola secara internal, jika binder dimiliki oleh proses lain. Membuat objek dengan cara lain akan menyebabkan kepemilikan ganda.

8. Lihat juga jenis Java/AIDL byte[].

Arah (masuk/keluar/masuk-keluar)

Saat menentukan jenis argumen untuk fungsi, Anda dapat menentukannya sebagai in, out, atau inout. Ini mengontrol arah informasi yang diteruskan untuk panggilan IPC. in adalah arah default, dan menunjukkan data diteruskan dari pemanggil ke tujuan panggilan. out berarti data diteruskan dari pemanggil ke pemanggil. inout adalah kombinasi dari keduanya. Namun, tim Android merekomendasikan agar Anda tidak menggunakan penentu argumen inout. Jika Anda menggunakan inout dengan antarmuka berversi dan pemanggil yang lebih lama, kolom tambahan yang hanya ada di pemanggil akan direset ke nilai defaultnya. Sehubungan dengan Rust, jenis inout normal menerima &mut Vec<T>, dan jenis inout daftar menerima &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);
}

UTF8/UTF16

Dengan backend CPP, Anda dapat memilih apakah string adalah utf-8 atau utf-16. Deklarasikan string sebagai @utf8InCpp String di AIDL untuk mengonversinya secara otomatis ke utf-8. Backend NDK dan Rust selalu menggunakan string utf-8. Untuk mengetahui informasi selengkapnya tentang anotasi utf8InCpp, lihat Anotasi di AIDL.

Nullability

Anda dapat menganotasi jenis yang dapat berupa null dengan @nullable. Untuk informasi selengkapnya tentang anotasi nullable, lihat Anotasi di AIDL.

Parcelable kustom

Parcelable kustom adalah parcelable yang diimplementasikan secara manual di backend target. Gunakan parcelable kustom hanya saat Anda mencoba menambahkan dukungan ke bahasa lain untuk parcelable kustom yang ada dan tidak dapat diubah.

Untuk mendeklarasikan parcelable kustom sehingga AIDL mengetahuinya, deklarasi parcelable AIDL akan terlihat seperti ini:

    package my.pack.age;
    parcelable Foo;

Secara default, langkah ini mendeklarasikan parcelable Java, dengan my.pack.age.Foo adalah class Java yang menerapkan antarmuka Parcelable.

Untuk deklarasi parcelable backend CPP kustom di AIDL, gunakan cpp_header:

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

Implementasi C++ di my/pack/age/Foo.h terlihat seperti ini:

    #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);
    };

Untuk deklarasi NDK kustom yang dapat dibagi di AIDL, gunakan ndk_header:

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

Implementasi NDK di android/pack/age/Foo.h terlihat seperti ini:

    #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);
    };

Di Android 15, untuk deklarasi Rust kustom yang dapat dipartisi di AIDL, gunakan rust_type:

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

Implementasi Rust di rust_crate/src/lib.rs terlihat seperti ini:

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

Kemudian, Anda dapat menggunakan parcelable ini sebagai jenis dalam file AIDL, tetapi tidak akan dihasilkan oleh AIDL. Berikan operator < dan == untuk parcelable kustom backend CPP/NDK untuk menggunakannya di union.

Nilai default

Parcelable terstruktur dapat mendeklarasikan nilai default per kolom untuk primitif, String, dan array dari jenis ini.

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

Di backend Java saat nilai default tidak ada, kolom diinisialisasi sebagai nilai nol untuk jenis primitif dan null untuk jenis non-primitif.

Di backend lain, kolom diinisialisasi dengan nilai yang diinisialisasi secara default saat nilai default tidak ditentukan. Misalnya, di backend C++, kolom String diinisialisasi sebagai string kosong dan kolom List<T> diinisialisasi sebagai vector<T> kosong. Kolom @nullable diinisialisasi sebagai kolom nilai null.

Serikat

Union AIDL diberi tag dan fiturnya serupa di semua backend. Kolom ini dibuat secara default ke nilai default kolom pertama dan memiliki cara khusus bahasa untuk berinteraksi dengannya.

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

Contoh Java

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

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

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

Contoh C++ dan 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)

Contoh Rust

Di Rust, union diimplementasikan sebagai enum dan tidak memiliki pengambil dan penyemat eksplisit.

    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

Penanganan error

Android OS menyediakan jenis error bawaan untuk digunakan layanan saat melaporkan error. Hal ini digunakan oleh binder dan dapat digunakan oleh layanan apa pun yang menerapkan antarmuka binder. Penggunaannya didokumentasikan dengan baik dalam definisi AIDL dan tidak memerlukan status atau jenis nilai yang ditentukan pengguna.

Parameter output dengan error

Saat fungsi AIDL melaporkan error, fungsi tersebut mungkin tidak melakukan inisialisasi atau mengubah parameter output. Secara khusus, parameter output dapat diubah jika error terjadi selama proses unparceling, bukan terjadi selama pemrosesan transaksi itu sendiri. Secara umum, saat mendapatkan error dari fungsi AIDL, semua parameter inout dan out serta nilai yang ditampilkan (yang bertindak seperti parameter out di beberapa backend) harus dianggap berada dalam status tidak terbatas.

Nilai error yang akan digunakan

Banyak nilai error bawaan yang dapat digunakan di antarmuka AIDL, tetapi beberapa diperlakukan dengan cara khusus. Misalnya, EX_UNSUPPORTED_OPERATION dan EX_ILLEGAL_ARGUMENT dapat digunakan saat mendeskripsikan kondisi error, tetapi EX_TRANSACTION_FAILED tidak boleh digunakan karena diperlakukan secara khusus oleh infrastruktur yang mendasarinya. Periksa definisi khusus backend untuk mengetahui informasi selengkapnya tentang nilai bawaan ini.

Jika antarmuka AIDL memerlukan nilai error tambahan yang tidak tercakup oleh jenis error bawaan, antarmuka tersebut dapat menggunakan error bawaan khusus layanan khusus yang memungkinkan penyertaan nilai error khusus layanan yang ditentukan oleh pengguna. Error khusus layanan ini biasanya ditentukan dalam antarmuka AIDL sebagai enum yang didukung const int atau int dan tidak diuraikan oleh binder.

Di Java, error dipetakan ke pengecualian, seperti android.os.RemoteException. Untuk pengecualian khusus layanan, Java menggunakan android.os.ServiceSpecificException bersama dengan error yang ditentukan pengguna.

Kode native di Android tidak menggunakan pengecualian. Backend CPP menggunakan android::binder::Status. Backend NDK menggunakan ndk::ScopedAStatus. Setiap metode yang dihasilkan oleh AIDL menampilkan salah satu dari ini, yang mewakili status metode. Backend Rust menggunakan nilai kode pengecualian yang sama dengan NDK, tetapi mengonversinya menjadi error Rust native (StatusCode, ExceptionCode) sebelum mengirimkannya kepada pengguna. Untuk error khusus layanan, Status atau ScopedAStatus yang ditampilkan menggunakan EX_SERVICE_SPECIFIC bersama dengan error yang ditentukan pengguna.

Jenis error bawaan dapat ditemukan dalam file berikut:

Backend Definisi
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

Menggunakan berbagai backend

Petunjuk ini khusus untuk kode platform Android. Contoh ini menggunakan jenis yang ditentukan, my.package.IFoo. Untuk petunjuk cara menggunakan backend Rust, lihat contoh AIDL Rust di halaman Pola Rust Android.

Jenis impor

Baik jenis yang ditentukan adalah antarmuka, parcelable, atau union, Anda dapat mengimpornya di Java:

import my.package.IFoo;

Atau di backend CPP:

#include <my/package/IFoo.h>

Atau di backend NDK (perhatikan namespace aidl tambahan):

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

Atau di backend Rust:

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

Meskipun dapat mengimpor jenis bertingkat di Java, Anda harus menyertakan header untuk jenis root-nya di backend CPP/NDK. Misalnya, saat mengimpor jenis bertingkat Bar yang ditentukan di my/package/IFoo.aidl (IFoo adalah jenis root file), Anda harus menyertakan <my/package/IFoo.h> untuk backend CPP (atau <aidl/my/package/IFoo.h> untuk backend NDK).

Menerapkan layanan

Untuk mengimplementasikan layanan, Anda harus mewarisi dari class stub native. Class ini membaca perintah dari driver binder dan menjalankan metode yang Anda implementasikan. Bayangkan Anda memiliki file AIDL seperti ini:

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

Di Java, Anda harus memperluas dari class ini:

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

Di backend CPP:

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

Di backend NDK (perhatikan namespace aidl tambahan):

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

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

Atau dengan Rust asinkron:

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

Mendaftar dan mendapatkan layanan

Layanan di platform Android biasanya terdaftar dengan proses servicemanager. Selain API di bawah, beberapa API memeriksa layanan (artinya API akan segera ditampilkan jika layanan tidak tersedia). Periksa antarmuka servicemanager yang sesuai untuk mengetahui detail persisnya. Operasi ini hanya dapat dilakukan saat mengompilasi dengan platform Android.

Di 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"));

Di backend 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"));

Di backend NDK (perhatikan namespace aidl tambahan):

    #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")));

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

Di backend Rust asinkron, dengan runtime thread tunggal:

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 will run on this thread.
    std::future::pending().await
}

Satu perbedaan penting dari opsi lainnya adalah kita tidak memanggil join_thread_pool saat menggunakan Rust asinkron dan runtime single-thread. Hal ini karena Anda perlu memberi Tokio thread tempatnya dapat menjalankan tugas yang dihasilkan. Dalam contoh ini, thread utama akan memenuhi tujuan tersebut. Setiap tugas yang dihasilkan menggunakan tokio::spawn akan dijalankan di thread utama.

Di backend Rust asinkron, dengan runtime multi-thread:

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

Dengan runtime Tokio multi-thread, tugas yang dihasilkan tidak dieksekusi di thread utama. Oleh karena itu, lebih baik memanggil join_thread_pool di thread utama sehingga thread utama tidak hanya diam. Anda harus menggabungkan panggilan dalam block_in_place untuk keluar dari konteks asinkron.

Anda dapat meminta untuk mendapatkan notifikasi saat layanan yang menghosting binder berhenti berfungsi. Hal ini dapat membantu menghindari kebocoran proxy callback atau membantu pemulihan error. Lakukan panggilan ini pada objek proxy binder.

  • Di Java, gunakan android.os.IBinder::linkToDeath.
  • Di backend CPP, gunakan android::IBinder::linkToDeath.
  • Di backend NDK, gunakan AIBinder_linkToDeath.
  • Di backend Rust, buat objek DeathRecipient, lalu panggil my_binder.link_to_death(&mut my_death_recipient). Perhatikan bahwa karena DeathRecipient memiliki callback, Anda harus mempertahankan objek tersebut selama Anda ingin menerima notifikasi.

Informasi penelepon

Saat menerima panggilan binder kernel, informasi pemanggil tersedia dalam beberapa API. PID (atau ID Proses) mengacu pada ID proses Linux dari proses yang mengirim transaksi. UID (atau User-ID) mengacu pada ID pengguna Linux. Saat menerima panggilan satu arah, PID panggilan adalah 0. Saat berada di luar konteks transaksi binder, fungsi ini menampilkan PID dan UID proses saat ini.

Di backend Java:

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

Di backend CPP:

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

Di backend NDK:

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

Di backend Rust, saat menerapkan antarmuka, tentukan hal berikut (bukan mengizinkannya menjadi default):

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

Laporan bug dan API proses debug untuk layanan

Saat laporan bug berjalan (misalnya, dengan adb bugreport), laporan tersebut akan mengumpulkan informasi dari seluruh sistem untuk membantu proses debug berbagai masalah. Untuk layanan AIDL, laporan bug menggunakan dumpsys biner di semua layanan yang terdaftar dengan pengelola layanan untuk membuang informasinya ke dalam laporan bug. Anda juga dapat menggunakan dumpsys di command line untuk mendapatkan informasi dari layanan dengan dumpsys SERVICE [ARGS]. Pada backend C++ dan Java, Anda dapat mengontrol urutan pembuangan layanan menggunakan argumen tambahan untuk addService. Anda juga dapat menggunakan dumpsys --pid SERVICE untuk mendapatkan PID layanan saat men-debug.

Untuk menambahkan output kustom ke layanan, Anda dapat mengganti metode dump di objek server seperti mengimplementasikan metode IPC lainnya yang ditentukan dalam file AIDL. Saat melakukannya, Anda harus membatasi dump ke izin aplikasi android.permission.DUMP atau membatasi dump ke UID tertentu.

Di backend Java:

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

Di backend CPP:

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

Di backend NDK:

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

Di backend Rust, saat menerapkan antarmuka, tentukan hal berikut (bukan mengizinkannya menjadi default):

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

Menggunakan pointer lemah

Anda dapat menyimpan referensi lemah ke objek binder.

Meskipun mendukung WeakReference, Java tidak mendukung referensi binder lemah di lapisan native.

Di backend CPP, jenis kelemahannya adalah wp<IFoo>.

Di backend NDK, gunakan ScopedAIBinder_Weak:

#include <android/binder_auto_utils.h>

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

Di backend Rust, Anda menggunakan WpIBinder atau Weak<IFoo>:

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

Mendapatkan deskriptor antarmuka secara dinamis

Deskripsi antarmuka mengidentifikasi jenis antarmuka. Hal ini berguna saat men-debug atau saat Anda memiliki binder yang tidak diketahui.

Di Java, Anda bisa mendapatkan deskripsi antarmuka dengan kode seperti:

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

Di backend CPP:

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

Backend NDK dan Rust tidak mendukung kemampuan ini.

Mendapatkan deskriptor antarmuka secara statis

Terkadang (seperti saat mendaftarkan layanan @VintfStability), Anda perlu mengetahui deskripsi antarmuka secara statis. Di Java, Anda bisa mendapatkan deskripsi dengan menambahkan kode seperti:

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

Di backend CPP:

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

Di backend NDK (perhatikan namespace aidl tambahan):

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

Di backend Rust:

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

Rentang enum

Di backend native, Anda dapat melakukan iterasi pada kemungkinan nilai yang dapat diambil oleh enum. Karena pertimbangan ukuran kode, hal ini tidak didukung di Java.

Untuk enum MyEnum yang ditentukan di AIDL, iterasi diberikan sebagai berikut.

Di backend CPP:

    ::android::enum_range<MyEnum>()

Di backend NDK:

   ::ndk::enum_range<MyEnum>()

Di backend Rust:

    MyEnum::enum_values()

Pengelolaan thread

Setiap instance libbinder dalam proses mempertahankan satu threadpool. Untuk sebagian besar kasus penggunaan, paket ini harus persis satu threadpool, yang digunakan bersama di semua backend. Satu-satunya pengecualian adalah saat kode vendor mungkin memuat salinan lain dari libbinder untuk berkomunikasi dengan /dev/vndbinder. Karena berada di node binder terpisah, threadpool tidak dibagikan.

Untuk backend Java, ukuran threadpool hanya dapat ditingkatkan (karena sudah dimulai):

    BinderInternal.setMaxThreads(<new larger value>);

Untuk backend CPP, operasi berikut tersedia:

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

Demikian pula, di backend NDK:

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

Di backend Rust:

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

Dengan backend Rust asinkron, Anda memerlukan dua threadpool: binder dan Tokio. Artinya, aplikasi yang menggunakan Rust asinkron memerlukan pertimbangan khusus, terutama dalam hal penggunaan join_thread_pool. Lihat bagian tentang mendaftarkan layanan untuk mengetahui informasi selengkapnya tentang hal ini.

Nama yang dicadangkan

C++, Java, dan Rust mencadangkan beberapa nama sebagai kata kunci atau untuk penggunaan khusus bahasa. Meskipun AIDL tidak menerapkan batasan berdasarkan aturan bahasa, penggunaan nama kolom atau jenis yang cocok dengan nama yang dicadangkan dapat menyebabkan kegagalan kompilasi untuk C++ atau Java. Untuk Rust, kolom atau jenis diganti namanya menggunakan sintaksis "ID mentah", yang dapat diakses menggunakan awalan r#.

Sebaiknya hindari penggunaan nama yang dicadangkan dalam definisi AIDL jika memungkinkan untuk menghindari binding yang tidak ergonomis atau kegagalan kompilasi langsung.

Jika sudah memiliki nama yang dicadangkan dalam definisi AIDL, Anda dapat mengganti nama kolom dengan aman sambil tetap kompatibel dengan protokol; Anda mungkin perlu memperbarui kode untuk melanjutkan build, tetapi program yang sudah di-build akan terus beroperasi bersama.

Nama yang harus dihindari: