Pedoman API AIDL

Praktik terbaik yang diuraikan di sini berfungsi sebagai panduan untuk mengembangkan antarmuka AIDL secara efektif dan dengan memperhatikan fleksibilitas antarmuka, terutama saat AIDL digunakan untuk menentukan API yang stabil dan kompatibel mundur.

AIDL dapat digunakan untuk menentukan API saat aplikasi perlu berinteraksi satu sama lain dalam proses latar belakang atau perlu berinteraksi dengan sistem.

AIDL Stabil dengan @VintfStability digunakan untuk antarmuka HAL dan memungkinkan klien dan server diupdate secara independen. Hal ini memerlukan kompatibilitas mundur dan data terstruktur.

Untuk mengetahui informasi selengkapnya tentang mengembangkan antarmuka pemrograman di aplikasi dengan AIDL, lihat Android Interface Definition Language (AIDL). Untuk contoh AIDL dalam praktik, lihat AIDL untuk HAL dan AIDL Stabil.

Pembuatan Versi

Setiap snapshot API AIDL yang kompatibel dengan versi sebelumnya sesuai dengan satu versi. Untuk mengambil snapshot, jalankan m <module-name>-freeze-api. Setiap kali klien atau server API dirilis (misalnya, dalam rilis Mainline), Anda perlu mengambil snapshot dan membuat versi baru. Untuk API sistem ke vendor, hal ini harus terjadi dengan revisi platform tahunan.

Jika antarmuka dibekukan (disimpan di direktori aidl_api yang diberi versi), antarmuka tersebut tidak boleh diubah. Anda hanya dapat mengedit direktori current. Anda dapat menambahkan metode dengan aman ke akhir antarmuka, kolom ke akhir parcelable, enumerator ke enum, dan anggota ke gabungan.

Klien yang memanggil metode baru di server lama akan menerima error UNKNOWN_TRANSACTION, yang harus ditangani dengan baik oleh klien.

Untuk mengetahui detail dan informasi selengkapnya tentang jenis perubahan yang diizinkan, lihat Membuat versi antarmuka.

Membuat dependensi

Modul Android tidak dapat bergantung pada beberapa versi yang berbeda dari library yang dihasilkan dari aidl_interface. Berbagai versi library menentukan jenis yang sama dalam namespace yang sama. Sistem build aidl Android mengidentifikasi masalah ini dan memunculkan error dengan setiap grafik dependensi yang berakhir pada versi library yang tidak cocok.

Hal ini dapat mempersulit update satu versi antarmuka umum saat modul berisi banyak dependensi dengan dependensinya sendiri.

Developer dapat menggunakan aidl_interface_defaults untuk mendeklarasikan dependensi antarmuka bersama pada antarmuka lain sehingga semuanya tidak perlu diupdate secara terpisah.

Sebaiknya gunakan modul *_defaults (seperti rust_defaults, cc_defaults, java_defaults) untuk mengatur dependensi pada library yang dihasilkan. Umumnya, ada default untuk antarmuka versi latest serta default untuk versi sebelumnya jika masih digunakan.

Developer dapat menggunakan aidl_interface_defaults untuk mendeklarasikan dependensi antarmuka bersama pada antarmuka lain sehingga semuanya tidak perlu diupdate secara terpisah.

Pedoman desain API

Umum

1. Dokumentasikan semuanya

  • Dokumentasikan setiap metode untuk semantik, argumen, penggunaan pengecualian bawaan, pengecualian khusus layanan, dan nilai yang ditampilkan.
  • Dokumentasikan setiap antarmuka untuk semantiknya.
  • Mendokumentasikan makna semantik enum dan konstanta.
  • Mendokumentasikan apa pun yang mungkin tidak jelas bagi pelaksana.
  • Berikan contoh jika relevan.

2. Casing

Gunakan huruf kapital di awal setiap kata (upper camel case) untuk jenis dan huruf kapital di awal kata kedua (lower camel case) untuk metode, kolom, dan argumen. Misalnya, MyParcelable untuk jenis parcelable dan anArgument untuk argumen. Untuk akronim, anggap akronim sebagai kata (NFC -> Nfc).

[-Wconst-name] Nilai dan konstanta enum harus berupa ENUM_VALUE dan CONSTANT_NAME

3. Menghindari kebutuhan akan pengetahuan global

API tidak boleh mengasumsikan bahwa developer memiliki pengetahuan global tentang seluruh codebase atau keahlian domain tertentu. Saat menangani ID khusus domain (seperti nama, ID, atau nama sebutan perangkat):

  • Tuliskan secara eksplisit dan dokumentasikan dari mana ID ini berasal dan formatnya jika penting bagi kedua sisi antarmuka untuk mengetahuinya.
  • Atau, gunakan ID khusus antarmuka (seperti objek binder atau token kustom) dan minta satu sisi mengelola pemetaan ke nilai yang mendasarinya. Hal ini mengurangi bentrokan dan menghindari pengguna harus memahami detail penerapan di luar area mereka.

4. Semua data terstruktur dan kompatibel dengan versi sebelumnya

Data tidak terstruktur seperti string, byte[], dan memori bersama harus memiliki format yang stabil untuk isinya, atau tidak transparan di satu sisi antarmuka.

Misalnya, argumen string yang digunakan sebagai pesan error untuk hasil dapat diterima dan dicatat untuk proses debug, tetapi tidak boleh diuraikan dan ditafsirkan karena format dan kontennya mungkin tidak kompatibel dengan versi sebelumnya. Jika sisi lain antarmuka perlu mengetahui jenis error saat runtime, gunakan enum, konstanta, atau ServiceSpecificException.

Demikian pula, jangan serialisasi objek ke byte[] atau memori bersama kecuali jika objek tersebut stabil dan kompatibel dengan versi sebelumnya. Dalam beberapa kasus, Anda dapat menggunakan anotasi @FixedSize untuk membagikan parcelable dan gabungan dalam memori bersama dan Antrean Pesan Cepat.

Antarmuka

1. Penamaan

[-Winterface-name] Nama antarmuka harus diawali dengan I seperti IFoo.

2. Hindari antarmuka besar dengan "objek" berbasis ID

Lebih memilih subantarmuka jika ada banyak panggilan yang terkait dengan API tertentu. Hal ini memberikan manfaat berikut:

  • Membuat kode klien atau server lebih mudah dipahami
  • Membuat siklus proses objek lebih sederhana
  • Memanfaatkan binder yang tidak dapat dipalsukan.

Tidak direkomendasikan: Satu antarmuka besar dengan objek berbasis ID

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Direkomendasikan: Antarmuka individual

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Jangan mencampur metode satu arah dengan dua arah

[-Wmixed-oneway] Jangan mencampur metode satu arah dengan metode non-satu arah, karena akan membuat pemahaman model threading menjadi rumit bagi klien dan server. Secara khusus, saat membaca kode klien dari antarmuka tertentu, Anda perlu mencari tahu apakah setiap metode akan memblokir atau tidak.

4. Menghindari kode status yang ditampilkan

Metode harus menghindari kode status sebagai nilai yang ditampilkan, karena semua metode AIDL memiliki kode status yang ditampilkan secara implisit. Lihat ServiceSpecificException atau EX_SERVICE_SPECIFIC. Menurut konvensi, nilai ini ditentukan sebagai konstanta dalam antarmuka AIDL. Jika penundaan kustom atau data error unik diperlukan bersama error, hanya saat itulah objek respons kustom harus merepresentasikan error. Untuk mengetahui informasi yang lebih mendetail, lihat Penanganan error.

5. Array sebagai parameter output dianggap berbahaya

[-Wout-array] Metode yang memiliki parameter output array, seperti void foo(out String[] ret) biasanya tidak baik karena ukuran array output harus dideklarasikan dan dialokasikan oleh klien di Java, sehingga ukuran output array tidak dapat dipilih oleh server. Perilaku yang tidak diinginkan ini terjadi karena cara kerja array di Java (tidak dapat dialokasikan ulang). Sebagai gantinya, gunakan API seperti String[] foo().

6. Menghindari parameter inout

[-Winout-parameter] Hal ini dapat membingungkan klien karena bahkan parameter in terlihat seperti parameter out.

7. Hindari parameter non-array @nullable out dan inout

[-Wout-nullable] Karena backend Java tidak menangani anotasi @nullable, sedangkan backend lain menanganinya, out/inout @nullable T dapat menyebabkan perilaku yang tidak konsisten di seluruh backend. Misalnya, backend non-Java dapat menyetel parameter @nullable keluar ke null (di C++, menyetelnya sebagai std::nullopt), tetapi klien Java tidak dapat membacanya sebagai null.

8. Menggunakan permintaan dan respons yang unik

Kelompokkan semua parameter yang diperlukan ke dalam satu input parcelable. Buat parcelable permintaan dan respons khusus untuk setiap metode antarmuka daripada meneruskan primitif (misalnya, gunakan ComputeResponse compute(in ComputeRequest request) daripada meneruskan variabel terpisah). Hal ini memungkinkan argumen baru ditambahkan nanti tanpa mengubah tanda tangan fungsi. Pola ini sangat disarankan jika diharapkan lebih banyak parameter dapat ditambahkan pada masa mendatang, atau jika suatu metode sudah memiliki lebih dari empat parameter.

Metode yang tidak memerlukan input atau output tambahan tidak akan mendapatkan manfaat dari saran ini. Memikirkan setiap kasus secara eksplisit dan tetap fleksibel untuk perubahan di masa mendatang dapat menghasilkan lebih sedikit metode yang tidak digunakan lagi dan lebih sedikit kompleksitas untuk kode yang kompatibel mundur.

Jika metode tidak dibuat menggunakan pola ini, Anda dapat beralih ke pola ini dengan membuat metode baru dengan permintaan dan respons yang dapat di-parcel dan menghentikan penggunaan metode lama. Contoh:

void foo(int a, int b, int c); // original version, but deprecated in favor of the next version
void fooV2(in MyArg arg); // new version having int a, b, c, and d.

Parcel terstruktur

1. Kapan digunakan

Gunakan parcelable terstruktur jika Anda memiliki beberapa jenis data untuk dikirim.

Atau, saat Anda memiliki satu jenis data, tetapi Anda memperkirakan bahwa Anda akan perlu memperluasnya pada masa mendatang. Misalnya, jangan gunakan String username. Gunakan parcelable yang dapat diperluas, seperti berikut:

parcelable User {
    String username;
}

Sehingga, di masa mendatang, Anda dapat memperluasnya, sebagai berikut:

parcelable User {
    String username;
    int id;
}

2. Menyediakan default secara eksplisit

[-Wexplicit-default, -Wenum-explicit-default] Berikan default eksplisit untuk kolom. Saat kolom baru ditambahkan ke parcelable, klien dan server lama akan menghapusnya, tetapi nilai default akan otomatis diisi untuk klien dan server baru.

3. Menggunakan ParcelableHolder untuk ekstensi vendor

Jika Anda menentukan parcelable AOSP yang perlu diperluas oleh pengimplementasi perangkat, sematkan instance ParcelableHolder dalam objek Anda. Hal ini berfungsi sebagai titik ekstensi tanpa menimbulkan konflik penggabungan. Hal ini mirip dengan ekstensi antarmuka terlampir tetapi memungkinkan penerap menyertakan parcelable eksklusif mereka bersama dengan parcelable yang ada tanpa membuat antarmuka dan jenis mereka sendiri.

4. Struktur data

  • Gunakan array atau List parcelable untuk merepresentasikan peta, karena AIDL tidak mendukung jenis Map secara native yang diterjemahkan dengan aman di semua backend native (misalnya, FeatureToScoreEntry[]).
  • Gunakan array objek parcelable untuk kolom berulang, bukan array primitif, untuk mencegah kebutuhan akan array paralel pada masa mendatang.
  • Gunakan objek parcelable yang memiliki jenis data yang kuat, bukan string yang diserialisasi atau JSON melalui IPC.
  • Gunakan enum, bukan boolean untuk status agar dapat diperluas pada masa mendatang. Untuk bitmask, gunakan jenis const int, bukan enum, untuk menghindari transmisi yang rumit di beberapa backend.

Parcelable tidak terstruktur

1. Kapan digunakan

Parcelable tidak terstruktur tersedia di Java dengan @JavaOnlyStableParcelable dan di backend NDK dengan @NdkOnlyStableParcelable. Biasanya, ini adalah parcelable lama dan yang sudah ada yang tidak dapat distrukturkan.

Konstanta dan enum

1. Bitfield harus menggunakan kolom konstanta

Bitfield harus menggunakan kolom konstanta (misalnya, const int FOO = 3; dalam antarmuka).

2. Enum harus berupa set tertutup.

Enum harus berupa set tertutup. Catatan: hanya pemilik antarmuka yang dapat menambahkan elemen enum. Jika vendor atau OEM perlu memperluas kolom ini, mekanisme alternatif diperlukan. Jika memungkinkan, sebaiknya gunakan fungsi vendor upstream. Namun, dalam beberapa kasus, nilai vendor kustom mungkin diizinkan melalui (meskipun, vendor harus memiliki mekanisme untuk membuat versi ini, mungkin AIDL itu sendiri, nilai tersebut tidak boleh saling bertentangan, dan nilai ini tidak boleh diekspos ke aplikasi pihak ketiga).

3. Hindari nilai seperti "NUM_ELEMENTS"

Karena enum diberi versi, nilai yang menunjukkan jumlah nilai yang ada harus dihindari. Di C++, hal ini dapat diatasi dengan enum_range<>. Untuk Rust, gunakan enum_values(). Di Java, belum ada solusinya.

Tidak direkomendasikan: Menggunakan nilai bernomor

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Menghindari awalan dan akhiran yang berlebihan

[-Wredundant-name] Hindari awalan dan akhiran yang berlebihan atau berulang dalam konstanta dan enumerator.

Tidak direkomendasikan: Menggunakan awalan yang berlebihan

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Direkomendasikan: Memberi nama enum secara langsung

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] Penggunaan FileDescriptor sebagai argumen atau nilai kembalian metode antarmuka AIDL sangat tidak disarankan. Terutama, saat AIDL diimplementasikan di Java, hal ini dapat menyebabkan kebocoran deskriptor file kecuali jika ditangani dengan hati-hati. Pada dasarnya, jika Anda menerima FileDescriptor, Anda harus menutupnya secara manual saat tidak lagi digunakan.

Untuk backend native, Anda aman karena FileDescriptor dipetakan ke unique_fd yang dapat ditutup otomatis. Namun, terlepas dari bahasa backend yang akan Anda gunakan, sebaiknya JANGAN menggunakan FileDescriptor sama sekali karena hal ini akan membatasi kebebasan Anda untuk mengubah bahasa backend di masa mendatang.

Sebagai gantinya, gunakan ParcelFileDescriptor, yang dapat ditutup secara otomatis.

Satuan variabel

Pastikan satuan variabel disertakan dalam nama sehingga satuannya ditentukan dengan baik dan dipahami tanpa perlu merujuk ke dokumentasi

Contoh

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Stempel waktu harus menunjukkan referensinya

Stempel waktu (bahkan semua unit!) harus menunjukkan unit dan titik referensinya dengan jelas.

Contoh

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;

Konkurensi dan operasi asinkron

Tangani operasi yang berjalan lama dengan antarmuka asinkron (oneway) untuk menghindari pemblokiran.

Jika layanan tidak memercayai kliennya, setiap callback yang diterimanya dari klien harus berupa antarmuka oneway. Tindakan ini mencegah klien dapat memblokir layanan tanpa batas waktu.

Struktur API asinkron yang terdiri dari panggilan penerusan, argumen input, dan antarmuka callback untuk mendapatkan hasil. Lihat Menggunakan permintaan dan respons unik untuk rekomendasi argumen.