Backend AIDL adalah target untuk pembuatan kode stub. Selalu gunakan file AIDL dalam bahasa tertentu dengan runtime tertentu. Bergantung pada konteksnya, Anda harus menggunakan backend AIDL yang berbeda.
Dalam tabel berikut, stabilitas permukaan API mengacu pada kemampuan
untuk mengompilasi kode terhadap permukaan API ini sehingga kode dapat
dikirimkan secara terpisah dari biner system.img
libbinder.so
.
AIDL memiliki backend berikut:
Backend | Bahasa | Platform API | Sistem build |
---|---|---|---|
Java | Java | SDK atau SystemApi (stabil*) |
Semua |
NDK | C++ | libbinder_ndk (stabil*) |
aidl_interface |
CPP | C++ | libbinder (tidak stabil) |
Semua |
Rust | Rust | libbinder_rs (stabil*) |
aidl_interface |
- Permukaan API ini stabil, tetapi banyak API, seperti API untuk pengelolaan layanan, dicadangkan untuk penggunaan platform internal dan tidak tersedia untuk aplikasi. Untuk mengetahui informasi selengkapnya tentang cara menggunakan AIDL di aplikasi, lihat Android Interface Definition Language (AIDL).
- Backend Rust diperkenalkan di Android 12; backend NDK telah tersedia mulai Android 10.
- Crate Rust dibangun di atas
libbinder_ndk
, yang membuatnya stabil dan portabel. APEX menggunakan binder crate dengan cara standar di sisi sistem. Bagian Rust digabungkan ke dalam APEX dan dikirimkan di dalamnya. Bagian ini bergantung padalibbinder_ndk.so
di partisi sistem.
Sistem build
Bergantung pada backend, ada dua cara untuk mengompilasi AIDL menjadi kode stub. Untuk mengetahui detail selengkapnya tentang sistem build, lihat Referensi Modul Soong.
Sistem build inti
Dalam cc_
atau java_
Android.bp module
(atau Android.mk
yang setara), Anda dapat menentukan file AIDL (.aidl
) sebagai file sumber. Dalam hal ini, backend Java atau CPP AIDL digunakan (bukan backend NDK), dan class untuk menggunakan file AIDL yang sesuai ditambahkan ke modul secara otomatis. Anda dapat menentukan opsi seperti local_include_dirs
(yang memberi tahu sistem build jalur root ke file AIDL dalam modul tersebut) di modul ini dalam grup aidl:
.
Backend Rust hanya untuk digunakan dengan
Rust. Modul rust_
ditangani secara berbeda karena file AIDL tidak
ditentukan sebagai file sumber. Sebagai gantinya, modul aidl_interface
menghasilkan
rustlib
bernama aidl_interface_name-rust
, yang dapat
ditautkan. Untuk mengetahui detailnya, lihat contoh AIDL Rust.
aidl_interface
Jenis yang digunakan dengan sistem build aidl_interface
harus terstruktur. Agar
terstruktur, parcelable harus berisi kolom secara langsung dan bukan
deklarasi jenis yang ditentukan langsung dalam bahasa target. Untuk mengetahui cara kerja AIDL terstruktur dengan AIDL stabil, lihat AIDL terstruktur versus AIDL stabil.
Jenis
Pertimbangkan compiler aidl
sebagai penerapan referensi untuk jenis.
Saat membuat antarmuka, panggil aidl --lang=<backend> ...
untuk melihat
file antarmuka yang dihasilkan. Saat menggunakan modul aidl_interface
, Anda dapat melihat
output di
out/soong/.intermediates/<path to module>/
.
Jenis Java atau AIDL | Jenis C++ | Jenis NDK | Jenis karat |
---|---|---|---|
boolean |
bool |
bool |
bool |
byte 8 |
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 |
Masuk: &str Keluar: String |
android.os.Parcelable |
android::Parcelable |
T/A | T/A |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder |
T[] |
std::vector<T> |
std::vector<T> |
Masuk: &[T] Keluar: Vec<T> |
byte[] |
std::vector |
std::vector 1 |
Masuk: &[u8] Keluar: Vec<u8> |
List<T> |
std::vector<T> 2 |
std::vector<T> 3 |
Masuk: In: &[T] 4Keluar: Vec<T> |
FileDescriptor |
android::base::unique_fd |
T/A | T/A |
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 gabungan (T )5 |
T |
T |
T |
T[N] 6 |
std::array<T, N> |
std::array<T, N> |
[T; N] |
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 tinggi, T
dapat berupa jenis non-primitif apa pun (termasuk jenis antarmuka), kecuali array. AOSP merekomendasikan penggunaan 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 apa pun kecuali array.
4. Jenis diteruskan secara berbeda untuk kode Rust, bergantung pada apakah jenis tersebut adalah input (argumen), atau output (nilai yang ditampilkan).
5. Jenis gabungan 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 binder SharedRefBase
, 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 menyebabkan
kepemilikan ganda.
8. Lihat juga jenis Java atau AIDL byte[]
.
Arah (masuk, keluar, dan masuk/keluar)
Saat menentukan jenis argumen ke fungsi, Anda dapat menentukannya sebagai in
, out
, atau inout
. Setelan ini mengontrol arah informasi yang diteruskan untuk panggilan IPC.
Penentu argumen
in
menunjukkan bahwa data diteruskan dari pemanggil ke penerima panggilan. Penentuin
adalah arah default, tetapi jika jenis data juga dapat berupaout
, Anda harus menentukan arahnya.Penentu argumen
out
berarti data diteruskan dari yang dipanggil ke pemanggil.Penentu argumen
inout
adalah kombinasi keduanya. Namun, sebaiknya hindari penggunaan penentu argumeninout
. Jika Anda menggunakaninout
dengan antarmuka berversi dan penerima panggilan yang lebih lama, kolom tambahan yang hanya ada di pemanggil akan disetel ulang ke nilai defaultnya. Sehubungan dengan Rust, jenisinout
normal menerima&mut T
, dan jenis daftarinout
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);
}
UTF-8 dan UTF-16
Dengan backend CPP, Anda dapat memilih apakah string menggunakan 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 utf8InCpp.
Nullability
Anda dapat menganotasi jenis yang dapat berupa null dengan @nullable
.
Untuk mengetahui informasi selengkapnya tentang anotasi nullable
, lihat
nullable.
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 sudah ada dan tidak dapat diubah.
Berikut adalah contoh deklarasi parcelable AIDL:
package my.pack.age;
parcelable Foo;
Secara default, ini mendeklarasikan parcelable Java dengan my.pack.age.Foo
adalah class Java yang mengimplementasikan 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 parcelable NDK kustom 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 parcelable Rust kustom 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
dibuat oleh AIDL. Sediakan operator <
dan ==
untuk backend CPP dan NDK
parcelable kustom untuk menggunakannya di union
.
Nilai default
Parcelable terstruktur dapat mendeklarasikan nilai default per kolom untuk kolom primitif,
kolom String
, dan array dari jenis ini.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Di backend Java, jika nilai default tidak ada, kolom diinisialisasi sebagai
nilai nol untuk jenis primitif dan null
untuk jenis nonprimitif.
Di backend lain, kolom diinisialisasi dengan nilai yang diinisialisasi default jika 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 bernilai null.
Gabungan
Gabungan AIDL diberi tag dan fiturnya serupa di semua backend. Dibuat ke nilai default kolom pertama dan memiliki cara berinteraksi khusus bahasa:
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.setStringField("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, gabungan diimplementasikan sebagai enum dan tidak memiliki getter dan setter 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
OS Android menyediakan jenis error bawaan untuk digunakan layanan saat melaporkan error. Objek 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 ditampilkan yang ditentukan pengguna.
Parameter output dengan error
Saat fungsi AIDL melaporkan error, fungsi mungkin tidak diinisialisasi atau
mengubah parameter output. Secara khusus, parameter output dapat dimodifikasi jika
error terjadi selama pembatalan pengiriman, 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 dalam status tidak pasti.
Nilai error yang akan digunakan
Banyak nilai error bawaan dapat digunakan di antarmuka AIDL mana pun, tetapi beberapa di antaranya ditangani dengan cara khusus. Misalnya, EX_UNSUPPORTED_OPERATION
dan
EX_ILLEGAL_ARGUMENT
boleh digunakan saat menjelaskan kondisi error, tetapi
EX_TRANSACTION_FAILED
tidak boleh digunakan karena ditangani 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
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 nilai ini, yang merepresentasikan status
metode. Backend Rust menggunakan nilai kode pengecualian yang sama dengan NDK, tetapi
mengonversinya menjadi error Rust native (StatusCode
, ExceptionCode
) sebelum
memberikannya 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 mengetahui petunjuk cara menggunakan backend Rust, lihat contoh AIDL Rust di pola Rust Android.
Jenis impor
Baik jenis yang ditentukan adalah antarmuka, parcelable, atau gabungan, 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 Anda dapat mengimpor jenis bertingkat di Java, di backend CPP dan NDK, Anda
harus menyertakan header untuk jenis root-nya. Misalnya, saat mengimpor jenis
bersarang Bar
yang ditentukan dalam 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).
Mengimplementasikan antarmuka
Untuk mengimplementasikan antarmuka, Anda harus mewarisi dari class stub native. Implementasi antarmuka sering disebut layanan saat didaftarkan
dengan pengelola layanan atau android.app.ActivityManager
dan disebut
panggilan balik saat didaftarkan oleh klien layanan. Namun, berbagai
nama digunakan untuk mendeskripsikan implementasi antarmuka, bergantung pada
penggunaannya. Class stub membaca perintah dari driver binder dan menjalankan metode yang Anda terapkan. Bayangkan Anda memiliki file AIDL seperti ini:
package my.package;
interface IFoo {
int doFoo();
}
Di Java, Anda harus memperluas dari class Stub
yang dihasilkan:
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(())
}
}
Mendaftarkan dan mendapatkan layanan
Layanan di Android platform biasanya didaftarkan dengan proses servicemanager
. Selain API berikut, beberapa API memeriksa layanan (artinya, API tersebut akan langsung memberikan hasil jika layanan tidak tersedia).
Periksa antarmuka servicemanager
yang sesuai untuk mengetahui detail pastinya. Anda dapat
melakukan operasi ini hanya saat mengompilasi dengan Android platform.
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 run on this thread.
std::future::pending().await
}
Salah satu perbedaan penting dari opsi lainnya adalah Anda tidak memanggil
join_thread_pool
saat menggunakan Rust asinkron dan runtime thread tunggal. Hal ini
karena Anda perlu memberi Tokio thread tempat Tokio dapat menjalankan tugas yang di-spawn. Dalam
contoh berikut, thread utama memenuhi tujuan tersebut. Semua tugas yang dibuat
menggunakan tokio::spawn
dieksekusi di thread utama.
Di backend Rust asinkron, dengan runtime multithread:
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 multithread, tugas yang dibuat tidak dijalankan di thread
utama. Oleh karena itu, akan lebih masuk akal untuk memanggil join_thread_pool
di thread
utama agar thread utama tidak menganggur. Anda harus menggabungkan panggilan dalam
block_in_place
untuk keluar dari konteks asinkron.
Kaitan dengan kematian
Anda dapat meminta untuk mendapatkan notifikasi saat layanan yang menghosting binder berhenti. 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 panggilmy_binder.link_to_death(&mut my_death_recipient)
. Perhatikan bahwa karenaDeathRecipient
memiliki callback, Anda harus menjaga objek tersebut tetap aktif selama Anda ingin menerima notifikasi.
Informasi penelepon
Saat menerima panggilan binder kernel, informasi pemanggil tersedia di beberapa API. ID proses (PID) mengacu pada ID proses Linux dari proses yang mengirim transaksi. ID pengguna (UI) merujuk pada ID pengguna Linux. Saat menerima panggilan satu arah, PID pemanggil adalah 0. Di luar konteks transaksi binder, fungsi ini menampilkan 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 menerapkan antarmuka, tentukan hal berikut (daripada membiarkannya 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 mengumpulkan
informasi dari seluruh sistem untuk membantu men-debug berbagai masalah.
Untuk layanan AIDL, laporan bug menggunakan dumpsys
biner di semua layanan yang terdaftar dengan pengelola layanan untuk mencadangkan informasinya ke dalam laporan bug. Anda juga dapat menggunakan dumpsys
di command line untuk mendapatkan informasi
dari layanan dengan dumpsys SERVICE [ARGS]
. Di backend C++ dan Java, Anda
dapat mengontrol urutan layanan yang di-dump dengan menggunakan argumen tambahan
ke addService
. Anda juga dapat menggunakan dumpsys --pid SERVICE
untuk mendapatkan PID
layanan saat melakukan proses debug.
Untuk menambahkan output kustom ke layanan Anda, ganti metode dump
di objek server Anda seperti saat Anda menerapkan metode IPC lainnya
yang ditentukan dalam file AIDL. Saat melakukannya, batasi dumping ke izin aplikasi android.permission.DUMP
atau batasi dumping 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 (daripada membiarkannya default):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Menggunakan pointer lemah
Anda dapat menyimpan referensi lemah ke objek binder.
Meskipun Java mendukung WeakReference
, Java tidak mendukung referensi binder lemah
di lapisan native.
Di backend CPP, jenis lemahnya 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, gunakan WpIBinder
atau Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Mendapatkan deskriptor antarmuka secara dinamis
Deskriptor antarmuka mengidentifikasi jenis antarmuka. Hal ini berguna saat men-debug atau saat Anda memiliki binder yang tidak diketahui.
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 kemampuan 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 native, Anda dapat melakukan iterasi pada kemungkinan nilai yang dapat diambil enum. Karena pertimbangan ukuran kode, hal ini tidak didukung di Java.
Untuk enum MyEnum
yang ditentukan dalam 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 suatu proses mempertahankan satu threadpool. Untuk sebagian besar kasus penggunaan, harus ada tepat satu threadpool, yang digunakan bersama di semua backend.
Satu-satunya pengecualian adalah jika kode vendor memuat salinan lain dari libbinder
untuk berkomunikasi dengan /dev/vndbinder
. Ini ada di node binder terpisah, sehingga
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 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 digunakan sistem
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 Anda jika memungkinkan untuk menghindari pengikatan 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 mengupdate kode untuk melanjutkan pembuatan, tetapi program yang sudah dibuat akan terus beroperasi.
Nama yang harus dihindari: