Backend AIDL

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

AIDL memiliki backend berikut:

Bagian belakang Bahasa permukaan API Membangun sistem
Jawa Jawa SDK/SystemApi (stabil*) semua
NDK C++ libbinder_ndk (stabil*) aidl_interface
CPP C++ libbinder (tidak stabil) semua
Karat Karat libbinder_rs (tidak stabil) aidl_interface
  • Permukaan API ini stabil, namun banyak API, seperti API untuk manajemen layanan, dicadangkan untuk penggunaan platform internal dan tidak tersedia untuk aplikasi. Untuk informasi selengkapnya tentang cara menggunakan AIDL di aplikasi, lihat dokumentasi pengembang .
  • Backend Rust diperkenalkan di Android 12; backend NDK telah tersedia mulai Android 10.
  • Peti Rust dibuat di atas libbinder_ndk . APEX menggunakan peti pengikat dengan cara yang sama seperti orang lain di sisi sistem. Bagian Rust dibundel ke dalam APEX dan dikirimkan di dalamnya. Itu tergantung pada libbinder_ndk.so di partisi sistem.

Membangun sistem

Tergantung pada backendnya, ada dua cara untuk mengkompilasi AIDL menjadi kode rintisan. Untuk detail lebih lanjut tentang sistem pembangunan, lihat Referensi Modul Soong .

Sistem pembangunan inti

Dalam modul Android.bp cc_ atau java_ mana pun (atau yang setara dengan Android.mk ), file .aidl dapat ditetapkan sebagai file sumber. Dalam hal ini, backend Java/CPP AIDL digunakan (bukan backend NDK), dan kelas yang menggunakan file AIDL terkait ditambahkan ke modul secara otomatis. Opsi seperti local_include_dirs , yang memberi tahu sistem build jalur root ke file AIDL dalam modul tersebut dapat ditentukan dalam modul ini di bawah grup aidl: Perhatikan bahwa backend Rust hanya untuk digunakan dengan Rust. modul rust_ ditangani secara berbeda karena file AIDL tidak ditentukan sebagai file sumber. Sebaliknya, modul aidl_interface menghasilkan rustlib bernama <aidl_interface name>-rust yang dapat dihubungkan. Untuk lebih jelasnya lihat contoh Rust AIDL .

aidl_interface

Tipe yang digunakan dengan sistem build ini harus terstruktur. Agar terstruktur, parsel harus berisi bidang secara langsung dan bukan berupa deklarasi tipe yang ditentukan secara langsung dalam bahasa target. Untuk mengetahui kecocokan AIDL terstruktur dengan AIDL stabil, lihat AIDL terstruktur versus stabil .

Jenis

Anda dapat mempertimbangkan kompiler aidl sebagai implementasi referensi untuk tipe. Saat Anda membuat antarmuka, aktifkan aidl --lang=<backend> ... untuk melihat file antarmuka yang dihasilkan. Saat Anda menggunakan modul aidl_interface , Anda dapat melihat output di out/soong/.intermediates/<path to module>/ .

Tipe Java/AIDL Tipe C++ Tipe NDK Jenis Karat
boolean bodoh bodoh bodoh
byte int8_t int8_t i8
arang karakter16_t karakter16_t u16
ke dalam int32_t int32_t i32
panjang int64_t int64_t i64
mengambang mengambang mengambang f32
dobel dobel dobel f64
Rangkaian android::String16 std::string Rangkaian
android.os.Parcelable android::Dapat diparsel T/A T/A
Pengikat I android::IBinder ndk::SpAIBinder pengikat::SpIBinder
T[] std::vektor<T> std::vektor<T> Dalam: &[T]
Keluar: Vec<T>
byte[] std::vektor<uint8_t> std::vektor<int8_t> 1 Dalam: &[u8]
Keluar: Vec<u8>
Daftar<T> std::vektor<T> 2 std::vektor<T> 3 Dalam: &[T] 4
Keluar: Vec<T>
Deskripsi File android::base::unique_fd T/A pengikat::parcel::ParcelFileDescriptor
Deskriptor ParcelFile android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor pengikat::parcel::ParcelFileDescriptor
tipe antarmuka (T) android::sp<T> std::bersama_ptr<T> pengikat::Kuat
tipe parsel (T) T T T
tipe serikat pekerja (T) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

2. Backend C++ mendukung List<T> di mana T adalah salah satu dari String , IBinder , ParcelFileDescriptor atau parcelable. Di Android 13 atau lebih tinggi, T dapat berupa tipe non-primitif apa pun (termasuk tipe antarmuka) kecuali array. AOSP menyarankan Anda menggunakan tipe array seperti T[] , karena tipe tersebut berfungsi di semua backend.

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

4. Tipe diteruskan secara berbeda untuk kode Rust bergantung pada apakah tipe tersebut merupakan input (argumen), atau output (nilai yang dikembalikan).

5. Jenis gabungan didukung di Android 12 dan lebih tinggi.

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

Arah (masuk/keluar/masuk)

Saat menentukan tipe argumen pada fungsi, Anda dapat menentukannya sebagai in , out , atau inout . Ini mengontrol ke arah mana informasi diteruskan untuk panggilan IPC. in adalah arah default, dan ini menunjukkan data diteruskan dari pemanggil ke penerima panggilan. out berarti data diteruskan dari penerima ke pemanggil. inout adalah kombinasi keduanya. Namun, tim Android menyarankan agar Anda menghindari penggunaan penentu argumen inout . Jika Anda menggunakan inout dengan antarmuka berversi dan callee yang lebih lama, bidang tambahan yang hanya ada di pemanggil akan diatur ulang ke nilai defaultnya. Sehubungan dengan Rust, tipe inout normal menerima &mut Vec<T> , dan tipe inout daftar menerima &mut Vec<T> .

UTF8/UTF16

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

Nullabilitas

Anda dapat menganotasi tipe yang bisa berupa null di backend Java dengan @nullable untuk mengekspos nilai null ke backend CPP dan NDK. Di backend Rust, tipe @nullable ini diekspos sebagai Option<T> . Server asli menolak nilai null secara default. Satu-satunya pengecualian untuk hal ini adalah tipe interface dan IBinder , yang selalu bernilai null untuk pembacaan NDK dan penulisan CPP/NDK. Untuk informasi selengkapnya tentang anotasi nullable , lihat Anotasi di AIDL .

Paket Khusus

Paket khusus adalah paket yang diterapkan secara manual di backend target. Gunakan paket khusus hanya ketika Anda mencoba menambahkan dukungan ke bahasa lain untuk paket khusus yang sudah ada dan tidak dapat diubah.

Untuk mendeklarasikan paket khusus sehingga AIDL mengetahuinya, deklarasi paket AIDL terlihat seperti ini:

    package my.pack.age;
    parcelable Foo;

Secara default, ini mendeklarasikan Java Parcelable dimana my.pack.age.Foo adalah kelas Java yang mengimplementasikan antarmuka Parcelable .

Untuk deklarasi backend CPP khusus yang dapat dipaketkan 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 mendeklarasikan paket NDK khusus 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 (eksperimen AOSP), untuk mendeklarasikan paket Rust khusus 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 parselable ini sebagai tipe dalam file AIDL, tetapi ini tidak akan dihasilkan oleh AIDL. Sediakan operator < dan == untuk paket khusus backend CPP/NDK untuk digunakan dalam union .

Nilai dasar

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

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

Di backend Java ketika nilai default tidak ada, bidang diinisialisasi sebagai nilai nol untuk tipe primitif dan null untuk tipe non-primitif.

Di backend lain, bidang diinisialisasi dengan nilai inisialisasi default ketika nilai default tidak ditentukan. Misalnya, di backend C++, kolom String diinisialisasi sebagai string kosong dan kolom List<T> diinisialisasi sebagai vector<T> kosong. Bidang @nullable diinisialisasi sebagai bidang nilai nol.

Penanganan Kesalahan

OS Android menyediakan jenis kesalahan bawaan untuk digunakan layanan saat melaporkan kesalahan. Ini digunakan oleh pengikat dan dapat digunakan oleh layanan apa pun yang mengimplementasikan antarmuka pengikat. Penggunaannya terdokumentasi dengan baik dalam definisi AIDL dan tidak memerlukan status atau jenis pengembalian apa pun yang ditentukan pengguna.

Parameter keluaran dengan kesalahan

Ketika fungsi AIDL melaporkan kesalahan, fungsi tersebut mungkin tidak menginisialisasi atau mengubah parameter output. Secara khusus, parameter output dapat diubah jika kesalahan terjadi selama proses unparceling dan bukan terjadi selama pemrosesan transaksi itu sendiri. Secara umum, saat mendapatkan kesalahan dari fungsi AIDL, semua parameter inout dan out serta nilai yang dikembalikan (yang berfungsi seperti parameter out di beberapa backend) harus dianggap berada dalam keadaan tidak terbatas.

Nilai kesalahan mana yang akan digunakan

Banyak nilai kesalahan bawaan yang dapat digunakan di antarmuka AIDL apa pun, namun ada pula yang diperlakukan dengan cara khusus. Misalnya, EX_UNSUPPORTED_OPERATION dan EX_ILLEGAL_ARGUMENT boleh digunakan saat menjelaskan kondisi kesalahan, namun EX_TRANSACTION_FAILED tidak boleh digunakan karena diperlakukan khusus oleh infrastruktur yang mendasarinya. Periksa definisi spesifik backend untuk informasi selengkapnya tentang nilai bawaan ini.

Jika antarmuka AIDL memerlukan nilai kesalahan tambahan yang tidak tercakup dalam jenis kesalahan bawaan, maka antarmuka AIDL dapat menggunakan kesalahan bawaan khusus layanan yang memungkinkan penyertaan nilai kesalahan khusus layanan yang ditentukan oleh pengguna. . Kesalahan khusus layanan ini biasanya ditentukan di antarmuka AIDL sebagai enum yang didukung const int atau int dan tidak diuraikan oleh pengikat.

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 asli di Android tidak menggunakan pengecualian. Backend CPP menggunakan android::binder::Status . Backend NDK menggunakan ndk::ScopedAStatus . Setiap metode yang dihasilkan oleh AIDL mengembalikan salah satu metode ini, yang mewakili status metode tersebut. Backend Rust menggunakan nilai kode pengecualian yang sama dengan NDK, namun mengubahnya menjadi error Rust asli ( StatusCode , ExceptionCode ) sebelum mengirimkannya ke pengguna. Untuk kesalahan khusus layanan, Status atau ScopedAStatus yang dikembalikan menggunakan EX_SERVICE_SPECIFIC bersama dengan kesalahan yang ditentukan pengguna.

Jenis kesalahan bawaan dapat ditemukan di file berikut:

Bagian belakang Definisi
Jawa android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Karat android/binder_status.h

Menggunakan berbagai backend

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

Mengimpor jenis

Baik tipe yang ditentukan adalah antarmuka, paket, atau gabungan, Anda dapat mengimpornya ke 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 Anda dapat mengimpor tipe bertingkat di Java, di backend CPP/NDK Anda harus menyertakan header untuk tipe rootnya. Misalnya, saat mengimpor Bar tipe bersarang yang ditentukan di my/package/IFoo.aidl ( IFoo adalah tipe root file), Anda harus menyertakan <my/package/IFoo.h> untuk backend CPP (atau <aidl/my/package/IFoo.h> untuk backend NDK).

Layanan implementasi

Untuk mengimplementasikan layanan, Anda harus mewarisi kelas stub asli. Kelas ini membaca perintah dari driver binder dan mengeksekusi metode yang Anda terapkan. Bayangkan Anda memiliki file AIDL seperti ini:

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

Di Java, Anda harus memperluas dari kelas 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 async:

    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 didaftarkan pada proses servicemanager . Selain API di bawah, beberapa API memeriksa layanan (artinya API tersebut segera kembali jika layanan tidak tersedia). Periksa antarmuka servicemanager yang sesuai untuk detail selengkapnya. Operasi ini hanya dapat dilakukan saat kompilasi terhadap platform Android.

Di Jawa:

    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 async, 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 kami tidak memanggil join_thread_pool saat menggunakan async Rust dan runtime thread tunggal. Ini karena Anda perlu memberi Tokio thread yang dapat menjalankan tugas yang muncul. Dalam contoh ini, thread utama akan melayani tujuan tersebut. Setiap tugas yang dihasilkan menggunakan tokio::spawn akan dijalankan di thread utama.

Di backend Rust async, 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-utas, tugas yang dihasilkan tidak dijalankan di thread utama. Oleh karena itu, lebih masuk akal untuk memanggil join_thread_pool pada thread utama agar thread utama tidak hanya menganggur. Anda harus menyelesaikan panggilan di block_in_place untuk keluar dari konteks async.

Anda dapat meminta untuk mendapatkan pemberitahuan ketika layanan yang menghosting pengikat mati. Hal ini dapat membantu menghindari kebocoran proxy panggilan balik atau membantu pemulihan kesalahan. Lakukan panggilan ini pada objek proxy pengikat.

  • 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 tersebut, Anda harus menjaga objek tersebut tetap hidup selama Anda ingin menerima notifikasi.

Informasi Penelepon

Saat menerima panggilan pengikat kernel, informasi pemanggil tersedia di beberapa API. PID (atau ID Proses) mengacu pada ID proses Linux dari proses yang mengirimkan transaksi. UID (atau ID Pengguna) mengacu pada ID pengguna Linux. Saat menerima panggilan satu arah, PID pemanggil adalah 0. Ketika berada di luar konteks transaksi pengikat, fungsi ini mengembalikan PID dan UID dari 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 mengimplementasikan antarmuka, tentukan hal berikut (alih-alih membiarkannya default):

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

Laporan bug dan API debugging untuk layanan

Saat laporan bug dijalankan (misalnya, dengan adb bugreport ), laporan bug tersebut mengumpulkan informasi dari seluruh sistem untuk membantu proses debug berbagai masalah. Untuk layanan AIDL, laporan bug menggunakan dumpsys biner pada semua layanan yang terdaftar pada manajer layanan untuk membuang informasinya ke dalam laporan bug. Anda juga dapat menggunakan dumpsys pada baris perintah untuk mendapatkan informasi dari layanan dengan dumpsys SERVICE [ARGS] . Di backend C++ dan Java, Anda dapat mengontrol urutan pembuangan layanan dengan menggunakan argumen tambahan ke addService . Anda juga dapat menggunakan dumpsys --pid SERVICE untuk mendapatkan PID layanan saat melakukan debug.

Untuk menambahkan keluaran khusus ke layanan Anda, Anda dapat mengganti metode dump di objek server Anda seperti Anda menerapkan metode IPC lain yang ditentukan dalam file AIDL. Saat melakukan ini, Anda harus membatasi pembuangan ke izin aplikasi android.permission.DUMP atau membatasi pembuangan 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 mengimplementasikan antarmuka, tentukan hal berikut (alih-alih membiarkannya default):

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

Mendapatkan deskriptor antarmuka secara dinamis

Deskriptor antarmuka mengidentifikasi jenis antarmuka. Ini berguna saat melakukan debug atau saat Anda memiliki pengikat yang tidak dikenal.

Di Java, Anda bisa mendapatkan deskriptor 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 fungsi ini.

Mendapatkan deskriptor antarmuka secara statis

Terkadang (seperti saat mendaftarkan layanan @VintfStability ), Anda perlu mengetahui deskriptor antarmuka secara statis. Di Java, Anda bisa mendapatkan deskriptor 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 asli, Anda dapat mengulangi kemungkinan nilai yang dapat diambil oleh enum. Karena pertimbangan ukuran kode, saat ini hal ini tidak didukung di Java.

Untuk enum MyEnum yang ditentukan dalam AIDL, iterasi disediakan sebagai berikut.

Di backend CPP:

    ::android::enum_range<MyEnum>()

Di backend NDK:

   ::ndk::enum_range<MyEnum>()

Di backend Rust:

    MyEnum::enum_values()

Manajemen benang

Setiap contoh libbinder dalam suatu proses memelihara satu threadpool. Untuk sebagian besar kasus penggunaan, ini harus berupa satu threadpool, yang dibagikan ke semua backend. Satu-satunya pengecualian untuk hal ini adalah ketika kode vendor mungkin memuat salinan libbinder lain untuk diajak bicara /dev/vndbinder . Karena ini berada pada node pengikat terpisah, threadpool tidak dibagikan.

Untuk backend Java, threadpool hanya dapat bertambah ukurannya (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 async, Anda memerlukan dua threadpool: binder dan Tokio. Artinya, aplikasi yang menggunakan async Rust memerlukan pertimbangan khusus, terutama saat menggunakan join_thread_pool . Lihat bagian pendaftaran layanan untuk informasi lebih lanjut mengenai hal ini.

Nama yang Dicadangkan

C++, Java, dan Rust mencadangkan beberapa nama sebagai kata kunci atau untuk penggunaan bahasa tertentu. Meskipun AIDL tidak menerapkan pembatasan berdasarkan aturan bahasa, penggunaan nama bidang atau tipe yang cocok dengan nama yang dicadangkan mungkin mengakibatkan kegagalan kompilasi untuk C++ atau Java. Untuk Rust, bidang atau tipe diganti namanya menggunakan sintaksis "pengidentifikasi mentah", yang dapat diakses menggunakan awalan r# .

Kami menyarankan Anda menghindari penggunaan nama khusus dalam definisi AIDL Anda jika memungkinkan untuk menghindari pengikatan yang tidak ergonomis atau kegagalan kompilasi langsung.

Jika Anda sudah memiliki nama khusus dalam definisi AIDL, Anda dapat dengan aman mengganti nama bidang namun tetap kompatibel dengan protokol; Anda mungkin perlu memperbarui kode Anda untuk terus membangun, tetapi program apa pun yang sudah dibangun akan terus beroperasi.

Nama yang harus dihindari: * Kata Kunci C++ * Kata Kunci Java * Kata Kunci Rust