Backend AIDL

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

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 panggil my_binder.link_to_death(&mut my_death_recipient) . Perhatikan bahwa karena DeathRecipient 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();