AIDL yang stabil

Android 10 menambahkan dukungan untuk Android Interface Definition Language (AIDL) yang stabil, cara baru untuk melacak antarmuka program aplikasi (API)/antarmuka biner aplikasi (ABI) yang disediakan oleh antarmuka AIDL. AIDL yang stabil memiliki perbedaan utama berikut dari AIDL:

  • Antarmuka didefinisikan dalam sistem build dengan aidl_interfaces .
  • Antarmuka hanya dapat berisi data terstruktur. Parcelable yang mewakili tipe yang diinginkan secara otomatis dibuat berdasarkan definisi AIDL mereka dan secara otomatis disusun dan tidak disusun.
  • Antarmuka dapat dideklarasikan sebagai stabil (kompatibel ke belakang). Ketika ini terjadi, API mereka dilacak dan diversi dalam file di sebelah antarmuka AIDL.

Mendefinisikan antarmuka AIDL

Definisi aidl_interface terlihat seperti ini:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name : Nama modul antarmuka AIDL yang secara unik mengidentifikasi antarmuka AIDL.
  • srcs : Daftar file sumber AIDL yang menyusun antarmuka. Jalur untuk tipe AIDL Foo yang didefinisikan dalam paket com.acme harus di <base_path>/com/acme/Foo.aidl , di mana <base_path> bisa berupa direktori apa pun yang terkait dengan direktori tempat Android.bp berada. Dalam contoh di atas, <base_path> adalah srcs/aidl .
  • local_include_dir : Jalur dari mana nama paket dimulai. Ini sesuai dengan <base_path> dijelaskan di atas.
  • imports : Daftar modul aidl_interface yang digunakan. Jika salah satu antarmuka AIDL Anda menggunakan antarmuka atau paket dari aidl_interface lain, masukkan namanya di sini. Ini bisa berupa nama itu sendiri, untuk merujuk ke versi terbaru, atau nama dengan akhiran versi (seperti -V1 ) untuk merujuk ke versi tertentu. Menentukan versi telah didukung sejak Android 12
  • versions : Versi antarmuka sebelumnya yang dibekukan di bawah api_dir , Mulai Android 11, versions dibekukan di bawah aidl_api/ name . Jika tidak ada versi beku dari antarmuka, ini tidak boleh ditentukan, dan tidak akan ada pemeriksaan kompatibilitas.
  • stability : Bendera opsional untuk janji stabilitas antarmuka ini. Saat ini hanya mendukung "vintf" . Jika ini tidak disetel, ini sesuai dengan antarmuka dengan stabilitas dalam konteks kompilasi ini (jadi antarmuka yang dimuat di sini hanya dapat digunakan dengan hal-hal yang dikompilasi bersama, misalnya pada system.img). Jika ini diatur ke "vintf" , ini sesuai dengan janji stabilitas: antarmuka harus tetap stabil selama digunakan.
  • gen_trace : Bendera opsional untuk mengaktifkan atau menonaktifkan pelacakan. Standarnya false .
  • host_supported : Bendera opsional yang bila disetel ke true membuat pustaka yang dihasilkan tersedia untuk lingkungan host.
  • unstable : Bendera opsional yang digunakan untuk menandai bahwa antarmuka ini tidak perlu stabil. Jika ini disetel ke true , sistem pembangunan tidak akan membuat dump API untuk antarmuka atau mengharuskannya untuk diperbarui.
  • backend.<type>.enabled : Bendera ini mengaktifkan setiap backend yang kodenya akan dihasilkan oleh kompiler AIDL. Saat ini, tiga backend didukung: java , cpp , dan ndk . Semua backend diaktifkan secara default. Ketika backend tertentu tidak diperlukan, itu harus dinonaktifkan secara eksplisit.
  • backend.<type>.apex_available : Daftar nama APEX yang tersedia untuk perpustakaan rintisan yang dihasilkan.
  • backend.[cpp|java].gen_log : Bendera opsional yang mengontrol apakah akan menghasilkan kode tambahan untuk mengumpulkan informasi tentang transaksi.
  • backend.[cpp|java].vndk.enabled : Bendera opsional untuk menjadikan antarmuka ini bagian dari VNDK. Standarnya false .
  • backend.java.platform_apis : Bendera opsional yang mengontrol apakah pustaka rintisan Java dibangun terhadap API pribadi dari platform. Ini harus diatur ke "true" ketika stability diatur ke "vintf" .
  • backend.java.sdk_version : Bendera opsional untuk menentukan versi SDK yang digunakan untuk membangun pustaka rintisan Java. Standarnya adalah "system_current" . Ini tidak boleh disetel ketika backend.java.platform_apis benar.
  • backend.java.platform_apis : Bendera opsional yang harus disetel ke true ketika pustaka yang dihasilkan perlu dibangun terhadap platform API daripada SDK.

Untuk setiap kombinasi versions dan backend yang diaktifkan, pustaka rintisan dibuat. Lihat Aturan penamaan modul untuk cara merujuk ke versi tertentu dari pustaka rintisan untuk backend tertentu.

Menulis file AIDL

Antarmuka dalam AIDL stabil mirip dengan antarmuka tradisional, dengan pengecualian bahwa mereka tidak diizinkan untuk menggunakan paket yang tidak terstruktur (karena ini tidak stabil!). Perbedaan utama dalam AIDL stabil adalah bagaimana parcelables didefinisikan. Sebelumnya, parcelables dideklarasikan ke depan ; dalam AIDL stabil, bidang dan variabel parcelable didefinisikan secara eksplisit.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Default saat ini didukung (tetapi tidak diperlukan) untuk boolean , char , float , double , byte , int , long , dan String . Di Android 12, default untuk enumerasi yang ditentukan pengguna juga didukung. Ketika default tidak ditentukan, nilai seperti 0 atau kosong digunakan. Pencacahan tanpa nilai default diinisialisasi ke 0 bahkan jika tidak ada pencacah nol.

Menggunakan perpustakaan rintisan

Setelah menambahkan pustaka rintisan sebagai dependensi ke modul Anda, Anda dapat memasukkannya ke dalam file Anda. Berikut adalah contoh stub library dalam sistem build ( Android.mk juga dapat digunakan untuk definisi modul lama):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}

Contoh di C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Contoh di Jawa:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Antarmuka versi

Mendeklarasikan modul dengan nama foo juga membuat target dalam sistem build yang dapat Anda gunakan untuk mengelola API modul. Saat dibuat, foo-freeze-api menambahkan definisi API baru di bawah api_dir atau aidl_api/ name , bergantung pada versi Android, dan menambahkan file .hash , keduanya mewakili versi antarmuka yang baru dibekukan. Membangun ini juga memperbarui properti versions untuk mencerminkan versi tambahan. Setelah properti versions ditentukan, sistem pembangunan menjalankan pemeriksaan kompatibilitas antara versi beku dan juga antara Top of Tree (ToT) dan versi beku terbaru.

Selain itu, Anda perlu mengelola definisi API versi ToT. Setiap kali API diperbarui, jalankan foo-update-api untuk memperbarui aidl_api/ name /current yang berisi definisi API versi ToT.

Untuk menjaga stabilitas antarmuka, pemilik dapat menambahkan yang baru:

  • Metode ke akhir antarmuka (atau metode dengan serial baru yang ditentukan secara eksplisit)
  • Elemen hingga akhir parcelable (memerlukan default untuk ditambahkan untuk setiap elemen)
  • Nilai konstan
  • Di Android 11, enumerator
  • Di Android 12, bidang hingga akhir serikat

Tidak ada tindakan lain yang diizinkan, dan tidak ada orang lain yang dapat memodifikasi antarmuka (jika tidak, mereka berisiko bertabrakan dengan perubahan yang dibuat oleh pemilik).

Untuk menguji apakah semua antarmuka dibekukan untuk rilis, Anda dapat membangun dengan set variabel lingkungan berikut:

  • AIDL_FROZEN_REL=true m ... - build membutuhkan semua antarmuka AIDL yang stabil untuk dibekukan yang tidak memiliki owner: bidang yang ditentukan.
  • AIDL_FROZEN_OWNERS="aosp test" - build membutuhkan semua antarmuka AIDL yang stabil untuk dibekukan dengan owner: bidang yang ditentukan sebagai "aosp" atau "test".

Menggunakan antarmuka berversi

Metode antarmuka

Saat runtime, ketika mencoba memanggil metode baru di server lama, klien baru mendapatkan kesalahan atau pengecualian, tergantung pada backend.

  • cpp backend mendapat ::android::UNKNOWN_TRANSACTION .
  • ndk backend mendapat STATUS_UNKNOWN_TRANSACTION .
  • backend java mendapatkan android.os.RemoteException dengan pesan yang mengatakan bahwa API tidak diimplementasikan.

Untuk strategi menangani ini, lihat versi kueri dan gunakan default .

Parcelable

Saat bidang baru ditambahkan ke parcelable, klien dan server lama akan menghapusnya. Ketika klien dan server baru menerima parcelable lama, nilai default untuk field baru akan terisi secara otomatis. Ini berarti bahwa default harus ditentukan untuk semua field baru dalam parcelable.

Klien tidak boleh mengharapkan server untuk menggunakan bidang baru kecuali mereka tahu server menerapkan versi yang memiliki bidang yang ditentukan (lihat versi kueri ).

Enum dan konstanta

Demikian pula, klien dan server harus menolak atau mengabaikan nilai konstanta dan enumerator yang tidak dikenali sebagaimana mestinya, karena lebih banyak dapat ditambahkan di masa mendatang. Misalnya, server tidak boleh dibatalkan ketika menerima enumerator yang tidak diketahuinya. Itu harus mengabaikannya, atau mengembalikan sesuatu sehingga klien tahu itu tidak didukung dalam implementasi ini.

serikat pekerja

Mencoba mengirim gabungan dengan bidang baru gagal jika penerima sudah tua dan tidak tahu tentang bidang tersebut. Implementasinya tidak akan pernah melihat penyatuan dengan bidang baru. Kegagalan diabaikan jika itu adalah transaksi satu arah; jika tidak, kesalahannya adalah BAD_VALUE (untuk backend C++ atau NDK) atau IllegalArgumentException (untuk backend Java). Kesalahan diterima jika klien mengirim kumpulan gabungan ke bidang baru ke server lama, atau saat klien lama menerima gabungan dari server baru.

Aturan penamaan modul

Di Android 11, untuk setiap kombinasi versi dan backend yang diaktifkan, modul library stub dibuat secara otomatis. Untuk merujuk ke modul perpustakaan rintisan khusus untuk penautan, jangan gunakan nama modul aidl_interface , tetapi nama modul perpustakaan rintisan, yaitu ifacename - version - backend , di mana

  • ifacename : nama modul aidl_interface
  • version adalah salah satu dari
    • V version-number untuk versi beku
    • V latest-frozen-version-number + 1 untuk versi tip-of-tree (belum-beku)
  • backend adalah salah satu dari
    • java untuk backend java,
    • cpp untuk backend C++,
    • ndk atau ndk_platform untuk backend NDK. Yang pertama untuk aplikasi, dan yang terakhir untuk penggunaan platform.

Asumsikan ada modul dengan nama foo dan versi terbarunya adalah 2 , dan mendukung NDK dan C++. Dalam hal ini, AIDL menghasilkan modul-modul ini:

  • Berdasarkan versi 1
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • Berdasarkan versi 2 (versi stabil terbaru)
    • foo-V2-(java|cpp|ndk|ndk_platform)
  • Berdasarkan versi ToT
    • foo-V3-(java|cpp|ndk|ndk_platform)

Dibandingkan dengan Android 11,

  • foo- backend , yang mengacu pada versi stabil terbaru menjadi foo- V2 - backend
  • foo-unstable- backend , yang mengacu pada versi ToT menjadi foo- V3 - backend

Nama file keluaran selalu sama dengan nama modul.

  • Berdasarkan versi 1: foo-V1-(cpp|ndk|ndk_platform).so
  • Berdasarkan versi 2: foo-V2-(cpp|ndk|ndk_platform).so
  • Berdasarkan versi ToT: foo-V3-(cpp|ndk|ndk_platform).so

Perhatikan bahwa kompiler AIDL tidak membuat modul versi yang unstable , atau modul non-versi untuk antarmuka AIDL yang stabil. Pada Android 12, nama modul yang dihasilkan dari antarmuka AIDL yang stabil selalu menyertakan versinya.

Metode antarmuka meta baru

Android 10 menambahkan beberapa metode antarmuka meta untuk AIDL yang stabil.

Menanyakan versi antarmuka objek jarak jauh

Klien dapat menanyakan versi dan hash antarmuka yang diterapkan objek jarak jauh dan membandingkan nilai yang dikembalikan dengan nilai antarmuka yang digunakan klien.

Contoh dengan cpp backend:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Contoh dengan ndk (dan ndk_platform ):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Contoh dengan backend java :

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Untuk bahasa Java, sisi jarak jauh HARUS mengimplementasikan getInterfaceVersion() dan getInterfaceHash() sebagai berikut:

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return IFoo.VERSION; }

    @Override
    public final String getInterfaceHash() { return IFoo.HASH; }
}

Ini karena kelas yang dihasilkan ( IFoo , IFoo.Stub , dll.) dibagi antara klien dan server (misalnya, kelas dapat berada di classpath boot). Saat kelas dibagikan, server juga ditautkan dengan versi kelas terbaru meskipun mungkin dibuat dengan versi antarmuka yang lebih lama. Jika antarmuka meta ini diimplementasikan di kelas bersama, itu selalu mengembalikan versi terbaru. Namun, dengan menerapkan metode seperti di atas, nomor versi antarmuka tertanam dalam kode server (karena IFoo.VERSION adalah static final int yang digariskan saat direferensikan) dan dengan demikian metode tersebut dapat mengembalikan versi persis server yang dibangun dengan.

Berurusan dengan antarmuka yang lebih lama

Ada kemungkinan bahwa klien diperbarui dengan versi antarmuka AIDL yang lebih baru tetapi server menggunakan antarmuka AIDL yang lama. Dalam kasus seperti itu, memanggil metode pada antarmuka lama mengembalikan UNKNOWN_TRANSACTION .

Dengan AIDL yang stabil, klien memiliki lebih banyak kontrol. Di sisi klien, Anda dapat mengatur implementasi default ke antarmuka AIDL. Sebuah metode dalam implementasi default dipanggil hanya ketika metode tersebut tidak diimplementasikan di sisi jarak jauh (karena dibuat dengan versi antarmuka yang lebih lama). Karena default diatur secara global, mereka tidak boleh digunakan dari konteks yang berpotensi dibagikan.

Contoh dalam C++ di Android T (AOSP eksperimental) dan yang lebih baru:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Contoh di Jawa:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

Anda tidak perlu menyediakan implementasi default semua metode dalam antarmuka AIDL. Metode yang dijamin untuk diimplementasikan di sisi jarak jauh (karena Anda yakin bahwa kendali jarak jauh dibuat ketika metode ada di deskripsi antarmuka AIDL) tidak perlu diganti di kelas impl default.

Mengubah AIDL yang ada menjadi AIDL terstruktur/stabil

Jika Anda memiliki antarmuka AIDL dan kode yang menggunakannya, gunakan langkah-langkah berikut untuk mengonversi antarmuka menjadi antarmuka AIDL yang stabil.

  1. Identifikasi semua dependensi antarmuka Anda. Untuk setiap paket yang bergantung pada antarmuka, tentukan apakah paket tersebut didefinisikan dalam AIDL yang stabil. Jika tidak ditentukan, paket harus dikonversi.

  2. Ubah semua parcelable di antarmuka Anda menjadi parcelable yang stabil (file antarmuka itu sendiri dapat tetap tidak berubah). Lakukan ini dengan mengekspresikan strukturnya secara langsung dalam file AIDL. Kelas manajemen harus ditulis ulang untuk menggunakan tipe baru ini. Ini dapat dilakukan sebelum Anda membuat paket aidl_interface (di bawah).

  3. Buat paket aidl_interface (seperti dijelaskan di atas) yang berisi nama modul Anda, dependensinya, dan informasi lain yang Anda butuhkan. Untuk membuatnya stabil (tidak hanya terstruktur), itu juga perlu diversi. Untuk informasi selengkapnya, lihat Antarmuka pembuatan versi .