Backend AIDL adalah target untuk 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 |
BPP | C++ | libbinder (tidak stabil) | semua |
Karat | Karat | libbinder_rs (tidak stabil) | aidl_interface |
- Permukaan API ini stabil, tetapi banyak API, seperti 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 pada Android 10.
- Peti Rust dibangun di atas
libbinder_ndk
. APEX menggunakan peti pengikat dengan cara yang sama seperti yang dilakukan orang lain di sisi sistem. Bagian Rust dibundel ke dalam APEX dan dikirim di dalamnya. Itu tergantung padalibbinder_ndk.so
pada partisi sistem.
Membangun sistem
Bergantung pada backend, ada dua cara untuk mengkompilasi AIDL ke dalam kode rintisan. Untuk detail selengkapnya tentang sistem build, lihat Referensi Modul Soong .
Sistem pembuatan inti
Dalam modul Android.bp cc_
atau java_
(atau setara Android.mk
), file .aidl
dapat ditetapkan sebagai file sumber. Dalam hal ini, backend Java/CPP AIDL digunakan (bukan backend NDK), dan kelas untuk menggunakan file AIDL yang sesuai ditambahkan ke modul secara otomatis. Opsi seperti local_include_dirs
, yang memberi tahu sistem build jalur root ke file AIDL dalam modul itu dapat ditentukan dalam modul ini di bawah aidl:
group. Perhatikan bahwa 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 dihubungkan. Untuk lebih jelasnya, lihat contoh Rust AIDL .
aidl_interface
Lihat AIDL Stabil . Jenis yang digunakan dengan sistem pembangunan ini harus terstruktur; yaitu, dinyatakan dalam AIDL secara langsung. Ini berarti bahwa parcelable khusus tidak dapat digunakan.
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 | Tipe Karat |
---|---|---|---|
boolean | bool | bool | bool |
byte | int8_t | int8_t | i8 |
arang | char16_t | char16_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 dikemas | T/A | T/A |
IBinder | 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> |
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> | pengikat::Kuat |
tipe paket (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 alih-alih 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 T (AOSP eksperimental) atau lebih tinggi, T
dapat berupa tipe non-primitif apa pun (termasuk tipe antarmuka) kecuali array. AOSP merekomendasikan agar Anda menggunakan tipe array seperti T[]
, karena mereka berfungsi di semua backend.
3. Backend NDK mendukung List<T>
di mana T
adalah salah satu String
, ParcelFileDescriptor
atau parcelable. Di Android T (AOSP eksperimental) atau lebih tinggi, T
dapat berupa jenis non-primitif apa pun kecuali array.
4. Jenis yang dikirimkan berbeda untuk kode Rust tergantung pada apakah mereka input (argumen), atau output (nilai yang dikembalikan).
5. Jenis serikat didukung di Android 12 dan lebih tinggi.
6. Di Android T (AOSP eksperimental) atau lebih tinggi, array ukuran tetap didukung. Array ukuran tetap dapat memiliki beberapa dimensi (misalnya int[3][4]
). Di backend Java, array ukuran tetap direpresentasikan sebagai tipe array.
Directionality (masuk/keluar/keluar)
Saat menentukan tipe argumen ke fungsi, Anda dapat menentukannya seperti in
, out
, atau inout
. Ini mengontrol di mana informasi arah dilewatkan untuk panggilan IPC. in
adalah arah default, dan ini menunjukkan data dilewatkan dari pemanggil ke penerima. out
berarti bahwa data dilewatkan dari callee ke pemanggil. inout
adalah kombinasi dari 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 disetel 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 string adalah utf-8 atau utf-16. Deklarasikan string sebagai @utf8InCpp String
di AIDL untuk secara otomatis mengonversinya 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 membuat anotasi jenis yang dapat 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 nol secara default. Satu-satunya pengecualian untuk ini adalah jenis interface
dan IBinder
, yang selalu bisa null untuk pembacaan NDK dan penulisan CPP/NDK. Untuk informasi selengkapnya tentang nullable
yang dapat dibatalkan, lihat Anotasi di AIDL .
Paket Kustom
Di backend C++ dan Java dalam sistem build inti, Anda dapat mendeklarasikan parcelable yang diimplementasikan secara manual di backend target (dalam C++ atau di Java).
package my.package;
parcelable Foo;
atau dengan deklarasi header C++:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Kemudian Anda dapat menggunakan parcelable ini sebagai tipe dalam file AIDL, tetapi tidak akan dihasilkan oleh AIDL.
Karat tidak mendukung paket khusus.
Nilai dasar
Paket terstruktur dapat mendeklarasikan nilai default per bidang untuk primitif, String
s, 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 default yang diinisialisasi saat nilai default tidak ditentukan. Misalnya, di backend C++, bidang String
diinisialisasi sebagai string kosong dan bidang List<T>
diinisialisasi sebagai vector<T>
. 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 didokumentasikan dengan baik dalam definisi AIDL dan mereka tidak memerlukan status atau tipe pengembalian yang ditentukan pengguna.
Ketika fungsi AIDL melaporkan kesalahan, fungsi mungkin tidak menginisialisasi atau mengubah parameter keluaran. Secara khusus, parameter output dapat dimodifikasi jika kesalahan terjadi selama unparceling yang bertentangan dengan yang terjadi selama pemrosesan transaksi itu sendiri. Secara umum, ketika mendapatkan kesalahan dari fungsi inout
, semua parameter masuk dan out
serta nilai kembalian (yang bertindak seperti parameter out
di beberapa backend) harus dianggap dalam keadaan tidak terbatas.
Jika antarmuka AIDL memerlukan nilai kesalahan tambahan yang tidak tercakup oleh jenis kesalahan bawaan, maka mereka dapat menggunakan kesalahan bawaan khusus layanan khusus yang memungkinkan penyertaan nilai kesalahan khusus layanan yang ditentukan oleh pengguna . Kesalahan khusus layanan ini biasanya didefinisikan dalam antarmuka AIDL sebagai enum
const int
atau int
-backed dan tidak diuraikan oleh pengikat.
Di Java, kesalahan dipetakan ke pengecualian, seperti android.os.RemoteException
. Untuk pengecualian khusus layanan, Java menggunakan android.os.ServiceSpecificException
bersama dengan kesalahan 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 satunya, yang mewakili status metode. Backend Rust menggunakan nilai kode pengecualian yang sama dengan NDK, tetapi mengubahnya menjadi kesalahan 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 |
BPP | 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
Apakah tipe yang ditentukan adalah antarmuka, parsel, 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 tipe bersarang di Java, di backend CPP/NDK Anda harus menyertakan header untuk tipe root-nya. 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).
Menerapkan layanan
Untuk mengimplementasikan layanan, Anda harus mewarisi dari kelas rintisan asli. Kelas ini membaca perintah dari driver pengikat dan mengeksekusi metode yang Anda terapkan. Bayangkan Anda memiliki file AIDL seperti ini:
package my.package;
interface IFoo {
int doFoo();
}
Di Jawa, Anda harus memperluas dari kelas ini:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
Di bagian belakang 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 bagian belakang 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(())
}
}
Mendaftar dan mendapatkan layanan
Layanan di platform Android biasanya terdaftar dengan proses servicemanager
. Selain API di bawah ini, beberapa API memeriksa layanan (artinya mereka segera kembali jika layanan tidak tersedia). Periksa antarmuka servicemanager
yang sesuai untuk detail yang tepat. Operasi ini hanya dapat dilakukan saat kompilasi terhadap platform Android.
Di Jawa:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
Di bagian belakang CPP:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// getting
status_t err = getService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
Di backend NDK (perhatikan namespace aidl
tambahan):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));
Di bagian belakang 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()
}
Menghubungkan dengan kematian
Anda dapat meminta untuk mendapatkan pemberitahuan ketika layanan yang menghosting pengikat mati. Ini dapat membantu menghindari kebocoran proxy panggilan balik atau membantu dalam 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 panggilmy_binder.link_to_death(&mut my_death_recipient)
. Perhatikan bahwa karenaDeathRecipient
memiliki callback, Anda harus menjaga objek itu tetap hidup selama Anda ingin menerima notifikasi.
Laporan bug dan debugging API untuk layanan
Saat laporan bug berjalan (misalnya, dengan adb bugreport
), mereka mengumpulkan informasi dari seluruh sistem untuk membantu men-debug berbagai masalah. Untuk layanan AIDL, laporan bug menggunakan dumpsys
biner pada semua layanan yang terdaftar dengan manajer layanan untuk membuang informasi mereka 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 debugging.
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 bagian belakang CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
Di bagian belakang NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Di backend Rust, saat mengimplementasikan antarmuka, tentukan yang 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 men-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 bagian belakang 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 apa itu deskriptor antarmuka secara statis. Di Java, Anda bisa mendapatkan deskriptor dengan menambahkan kode seperti:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Di bagian belakang 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 bagian belakang Rust:
aidl::my::package::BnFoo::get_descriptor()
Rentang Enum
Di backend asli, Anda dapat mengulangi nilai yang mungkin diambil oleh enum. Karena pertimbangan ukuran kode, ini tidak didukung di Java saat ini.
Untuk enum MyEnum
yang didefinisikan dalam AIDL, iterasi diberikan sebagai berikut.
Di bagian belakang CPP:
::android::enum_range<MyEnum>()
Di bagian belakang NDK:
::ndk::enum_range<MyEnum>()
Di bagian belakang Rust:
MyEnum::enum_range()
Manajemen utas
Setiap instance libbinder
dalam suatu proses mempertahankan satu threadpool. Untuk sebagian besar kasus penggunaan, ini harus tepat satu threadpool, dibagikan di semua backend. Satu-satunya pengecualian untuk ini adalah ketika kode vendor mungkin memuat salinan libbinder
lain untuk berbicara dengan /dev/vndbinder
. Karena ini berada di simpul pengikat yang terpisah, kumpulan utas tidak dibagikan.
Untuk backend Java, ukuran threadpool hanya dapat bertambah (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 bagian belakang Rust:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();