安定したAIDL

Android 10 では、安定した Android Interface Definition Language (AIDL) のサポートが追加されました。これは、AIDL インターフェースによって提供されるアプリケーション プログラム インターフェース (API)/アプリケーション バイナリ インターフェース (ABI) を追跡する新しい方法です。安定した AIDL には、AIDL との次の主な違いがあります。

  • インターフェイスは、ビルド システムでaidl_interfacesを使用して定義されます。
  • インターフェイスには、構造化データのみを含めることができます。必要なタイプを表す Parcelable は、AIDL 定義に基づいて自動的に作成され、自動的にマーシャリングおよびアンマーシャリングされます。
  • インターフェースは、安定 (後方互換性) として宣言できます。これが発生すると、API が追跡され、AIDL インターフェースの横にあるファイルでバージョン管理されます。

AIDL インターフェースの定義

aidl_interfaceの定義は次のようになります。

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

}
  • name : AIDL インターフェースを一意に識別する AIDL インターフェース モジュールの名前。
  • srcs : インターフェイスを構成する AIDL ソース ファイルのリスト。パッケージcom.acmeで定義された AIDL タイプFooのパスは<base_path>/com/acme/Foo.aidlにある必要があります。ここで、 <base_path> Android.bpがあるディレクトリに関連する任意のディレクトリです。上記の例では、 <base_path>srcs/aidlです。
  • local_include_dir : パッケージ名が始まるパス。上記で説明した<base_path>に相当します。
  • imports : これが使用するaidl_interfaceモジュールのリスト。 AIDL インターフェイスの 1 つが別のaidl_interfaceからのインターフェイスまたは parcelable を使用する場合は、その名前をここに入力します。これは、最新バージョンを参照する名前自体、または特定のバージョンを参照するバージョン サフィックス ( -V1など) を含む名前にすることができます。バージョンの指定は Android 12 以降でサポートされています
  • versions : api_dirの下で凍結されたインターフェースの以前のバージョン。Android 11 以降、 versions aidl_api/ nameの下で凍結されます。インターフェイスの凍結されたバージョンがない場合、これは指定されるべきではなく、互換性チェックは行われません。このフィールドは、13 以降ではversions_with_infoに置き換えられました。
  • versions_with_info : タプルのリスト。各タプルには、凍結されたバージョンの名前と、このバージョンの aidl_interface がインポートした他の aidl_interface モジュールのバージョン インポートのリストが含まれます。 AIDL インターフェイス IFACE のバージョン V の定義はaidl_api/ IFACE / Vにあります。このフィールドは Android 13 で導入されたものであり、Android.bp で直接変更することは想定されていません。フィールドは、 *-update-apiまたは*-freeze-apiを呼び出すことによって追加または更新されます。また、ユーザーが*-update-apiまたは*-freeze-apiを呼び出すと、 versionsフィールドは自動的にversions_with_infoに移行されます。
  • stability : このインターフェースの安定性を約束するためのオプションのフラグ。現在は"vintf"のみをサポートしています。これが設定されていない場合、これはこのコンパイル コンテキスト内で安定したインターフェイスに対応します (したがって、ここにロードされたインターフェイスは、system.img などで一緒にコンパイルされたものでのみ使用できます)。これが"vintf"に設定されている場合、これは安定性の約束に対応します: インターフェイスは、使用される限り安定した状態に保たれなければなりません。
  • gen_trace : トレースをオンまたはオフにするオプションのフラグ。デフォルトはfalseです。
  • host_supported : trueに設定すると、生成されたライブラリをホスト環境で使用できるようにするオプションのフラグ。
  • unstable : このインターフェースが安定している必要がないことを示すために使用されるオプションのフラグ。これがtrueに設定されている場合、ビルド システムはインターフェイスの API ダンプを作成せず、更新も必要としません。
  • frozen : オプションのフラグで、 trueに設定すると、以前のバージョンのインターフェイスからインターフェイスが変更されていないことを意味します。これにより、より多くのビルド時のチェックが可能になります。 falseに設定すると、インターフェイスが開発中であり、新しい変更があることを意味するため、 foo-freeze-apiを実行すると新しいバージョンが生成され、値が自動的にtrueに変更されます。 Android 14 で導入されました(AOSP 試験運用版)。
  • backend.<type>.enabled : これらのフラグは、AIDL コンパイラがコードを生成する各バックエンドを切り替えます。現在、Java、C++、NDK、Rust の 4 つのバックエンドがサポートされています。 Java、C++、および NDK バックエンドはデフォルトで有効になっています。これら 3 つのバックエンドのいずれかが不要な場合は、明示的に無効にする必要があります。 Rust はデフォルトで無効になっています。
  • backend.<type>.apex_available : 生成されたスタブ ライブラリを使用できる APEX 名のリスト。
  • backend.[cpp|java].gen_log : トランザクションに関する情報を収集するための追加コードを生成するかどうかを制御するオプションのフラグ。
  • backend.[cpp|java].vndk.enabled : このインターフェイスを VNDK の一部にするオプションのフラグ。デフォルトはfalseです。
  • backend.java.sdk_version : Java スタブ ライブラリがビルドされる SDK のバージョンを指定するためのオプションのフラグ。デフォルトは"system_current"です。 backend.java.platform_apisが true の場合、これを設定しないでください。
  • backend.java.platform_apis : 生成されたライブラリを SDK ではなくプラットフォーム API に対してビルドする必要がある場合にtrueに設定する必要があるオプションのフラグ。

バージョンと有効なバックエンドの組み合わせごとに、スタブ ライブラリが作成されます。特定のバックエンドのスタブ ライブラリの特定のバージョンを参照する方法については、モジュールの命名規則 を参照してください。

AIDL ファイルの書き込み

安定 AIDL のインターフェースは従来のインターフェースと似ていますが、構造化されていないパーセルブルを使用できない点が異なります (これらは安定していないためです!)。安定した AIDL の主な違いは、Parcelable の定義方法です。以前は、parcelable は前方宣言されていました。安定した AIDL では、parcelables フィールドと変数が明示的に定義されています。

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

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

booleancharfloatdoublebyteintlong 、およびStringのデフォルトが現在サポートされています (必須ではありません)。 Android 12 では、ユーザー定義の列挙型のデフォルトもサポートされています。デフォルトが指定されていない場合、0 のような値または空の値が使用されます。デフォルト値のない列挙は、ゼロ列挙子がない場合でも 0 に初期化されます。

スタブ ライブラリの使用

モジュールへの依存関係としてスタブ ライブラリを追加した後、それらをファイルに含めることができます。ビルド システムのスタブ ライブラリの例を次に示します ( Android.mkレガシー モジュール定義にも使用できます)。

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"],
    ...
}
# or
rust_... {
    name: ...,
    rust_libs: ["my-module-name-rust"],
    ...
}

C++ での例:

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

Java での例:

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

Rust での例:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

バージョニング インターフェイス

モジュールをfooという名前で宣言すると、モジュールの API を管理するために使用できるターゲットがビルド システムに作成されます。ビルドすると、 foo-freeze-api は、 Android のバージョンに応じてapi_dirまたはaidl_api/ nameの下に新しい API 定義を追加し、 .hashファイルを追加します。どちらもインターフェースの新しく凍結されたバージョンを表します。 foo-freeze-api はversions_with_infoプロパティも更新して、追加のバージョンとそのバージョンのimportsを反映します。基本的に、 versions_with_infoimports importsフィールドからコピーされます。ただし、明示的なバージョンを持たないimports場合、最新の安定バージョンは、 versions_with_infoのインポートで指定されます。 versions_with_infoプロパティが指定されると、ビルド システムは凍結されたバージョン間、およびトップ オブ ツリー (ToT) と最新の凍結されたバージョンの間で互換性チェックを実行します。

また、ToT版のAPI定義を管理する必要があります。 API が更新されるたびに、 foo-update-apiを実行して、ToT バージョンの API 定義を含むaidl_api/ name /currentを更新します。

インターフェイスの安定性を維持するために、所有者は新しいものを追加できます:

  • インターフェイスの最後までのメソッド (または明示的に定義された新しいシリアルを持つメソッド)
  • パーセルブルの末尾までの要素 (要素ごとにデフォルトを追加する必要があります)
  • 定数値
  • Android 11 では、列挙子
  • Android 12 では、ユニオンの最後までのフィールド

他のアクションは許可されず、他の誰もインターフェイスを変更できません (そうしないと、所有者が行った変更と競合する危険があります)。

すべてのインターフェイスがリリースのために凍結されていることをテストするには、次の環境変数を設定してビルドできます。

  • AIDL_FROZEN_REL=true m ... - ビルドでは、 owner:フィールドが指定されていないすべての安定した AIDL インターフェイスを凍結する必要があります。
  • AIDL_FROZEN_OWNERS="aosp test" - ビルドには、「aosp」または「test」として指定されたowner:フィールドですべての安定した AIDL インターフェースを凍結する必要があります。

輸入の安定

インターフェイスの凍結されたバージョンのインポートのバージョンを更新すると、Stable AIDL レイヤーで下位互換性があります。ただし、これらを更新するには、古いバージョンのインターフェイスを使用するすべてのサーバーとクライアントを更新する必要があり、異なるバージョンのタイプを混在させると一部のアプリケーションが混乱する可能性があります。一般に、型のみまたは共通のパッケージの場合、IPC トランザクションから不明な型を処理するコードを既に作成しておく必要があるため、これは安全です。

Android プラットフォーム コードでは、 android.hardware.graphics.commonがこのタイプのバージョン アップグレードの最大の例です。

バージョン管理されたインターフェースの使用

インターフェイス メソッド

実行時に、古いサーバーで新しいメソッドを呼び出そうとすると、新しいクライアントはバックエンドに応じてエラーまたは例外を受け取ります。

  • cppバックエンドは::android::UNKNOWN_TRANSACTIONを取得します。
  • ndkバックエンドはSTATUS_UNKNOWN_TRANSACTIONを取得します。
  • javaバックエンドがandroid.os.RemoteExceptionを取得し、API が実装されていないことを示すメッセージが表示されます。

これを処理する方法については、バージョンの照会デフォルトの使用を参照してください。

パーセルブル

Parcelables に新しいフィールドが追加されると、古いクライアントとサーバーはそれらを削除します。新しいクライアントとサーバーが古い Parcelable を受け取ると、新しいフィールドのデフォルト値が自動的に入力されます。これは、Parcelable のすべての新しいフィールドにデフォルトを指定する必要があることを意味します。

クライアントは、サーバーがフィールドが定義されたバージョンを実装していることを認識していない限り、サーバーが新しいフィールドを使用することを期待すべきではありません (バージョンの照会を参照してください)。

列挙型と定数

同様に、クライアントとサーバーは、認識されていない定数値と列挙子を必要に応じて拒否または無視する必要があります。これは、将来さらに追加される可能性があるためです。たとえば、サーバーが認識していない列挙子を受け取った場合、サーバーは中止されるべきではありません。それを無視するか、何かを返して、この実装ではサポートされていないことをクライアントが認識できるようにする必要があります。

組合

受信者が古く、フィールドを認識していない場合、新しいフィールドでユニオンを送信しようとしても失敗します。実装では、新しいフィールドとのユニオンは表示されません。一方向のトランザクションの場合、失敗は無視されます。それ以外の場合、エラーはBAD_VALUE (C++ または NDK バックエンドの場合) またはIllegalArgumentException (Java バックエンドの場合) です。クライアントが新しいフィールドに設定されたユニオンを古いサーバーに送信している場合、または古いクライアントが新しいサーバーからユニオンを受信して​​いる場合、エラーが発生します。

モジュールの命名規則

Android 11 では、バージョンと有効なバックエンドの組み合わせごとに、スタブ ライブラリ モジュールが自動的に作成されます。リンクのために特定のスタブ ライブラリ モジュールを参照するには、 aidl_interfaceモジュールの名前ではなく、スタブ ライブラリ モジュールの名前 ( ifacename - version - backendを使用します。

  • ifacename : aidl_interfaceモジュールの名前
  • version次のいずれかです
    • 凍結されたバージョンのV version-number
    • V latest-frozen-version-number + 1ツリーの先端 (まだ凍結されていない) バージョンの場合)
  • backend次のいずれかです
    • Java バックエンド用のjava
    • C++ バックエンドのcpp
    • NDK バックエンドのndkまたはndk_platform 。前者はアプリ用、後者はプラットフォーム用、
    • Rust バックエンドのrust

fooという名前のモジュールがあり、その最新バージョンが2であり、NDK と C++ の両方をサポートしているとします。この場合、AIDL は次のモジュールを生成します。

  • バージョン 1 に基づく
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • バージョン 2 (最新の安定版) に基づく
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • ToT バージョンに基づく
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Android 11と比較すると、

  • 最新の安定版を指すfoo- backendfoo- V2 - backendになります
  • ToT バージョンを参照するfoo-unstable- backendfoo- V3 - backendになります

出力ファイル名は常にモジュール名と同じです。

  • バージョン 1 に基づく: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • バージョン 2 に基づく: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • ToT バージョンに基づく: foo-V3-(cpp|ndk|ndk_platform|rust).so

AIDL コンパイラは、 unstableバージョンのモジュールも、安定した AIDL インターフェイス用のバージョン管理されていないモジュールも作成しないことに注意してください。 Android 12 以降、安定した AIDL インターフェースから生成されたモジュール名には常にそのバージョンが含まれます。

新しいメタ インターフェイス メソッド

Android 10 では、安定した AIDL 用のいくつかのメタ インターフェース メソッドが追加されています。

リモート オブジェクトのインターフェイス バージョンのクエリ

クライアントは、リモート オブジェクトが実装しているインターフェイスのバージョンとハッシュを照会し、返された値をクライアントが使用しているインターフェイスの値と比較できます。

cppバックエンドの例:

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();

ndk (および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);

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();

Java 言語の場合、リモート側は次のようにgetInterfaceVersion()getInterfaceHash()を実装する必要があります (コピー/貼り付けの間違いを避けるためにIFooの代わりにsuperを使用します。状況によっては、警告を無効にするために@SuppressWarnings("static")アノテーションが必要になる場合がありますjavac構成):

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

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

これは、生成されたクラス ( IFooIFoo.Stubなど) がクライアントとサーバーの間で共有されるためです (たとえば、クラスはブート クラスパスにある可能性があります)。クラスが共有されると、サーバーは、古いバージョンのインターフェースで構築された可能性がある場合でも、クラスの最新バージョンに対してリンクされます。このメタ インターフェイスが共有クラスに実装されている場合、常に最新バージョンが返されます。ただし、上記のようにメソッドを実装することにより、インターフェイスのバージョン番号がサーバーのコードに埋め込まれ ( IFoo.VERSIONは、参照時にインライン化されるstatic final intであるため)、メソッドはサーバーが構築された正確なバージョンを返すことができます。と。

古いインターフェースの扱い

クライアントが新しいバージョンの AIDL インターフェースで更新されているにもかかわらず、サーバーが古い AIDL インターフェースを使用している可能性があります。このような場合、古いインターフェースでメソッドを呼び出すと、 UNKNOWN_TRANSACTIONが返されます。

安定した AIDL を使用すると、クライアントはより詳細に制御できます。クライアント側では、デフォルトの実装を AIDL インターフェースに設定できます。既定の実装のメソッドは、そのメソッドがリモート側で実装されていない場合にのみ呼び出されます (古いバージョンのインターフェイスで構築されたため)。デフォルトはグローバルに設定されるため、共有される可能性のあるコンテキストからは使用しないでください。

Android 13 以降の C++ での例:

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

Java での例:

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


foo.anAddedMethod(...);

AIDL インターフェースのすべてのメソッドのデフォルト実装を提供する必要はありません。リモート側で実装されることが保証されているメソッド (メソッドが AIDL インターフェースの記述にあるときにリモートがビルドされることが確実であるため) は、デフォルトのimplクラスでオーバーライドする必要はありません。

既存の AIDL を構造化された安定した AIDL に変換する

既存の AIDL インターフェースとそれを使用するコードがある場合は、次の手順を使用してインターフェースを安定した AIDL インターフェースに変換します。

  1. インターフェイスのすべての依存関係を特定します。インターフェイスが依存するすべてのパッケージについて、パッケージが安定した AIDL で定義されているかどうかを確認します。定義されていない場合、パッケージを変換する必要があります。

  2. インターフェイス内のすべての parcelable を安定した parcelable に変換します (インターフェイス ファイル自体は変更されないままにすることができます)。これを行うには、AIDL ファイルで構造を直接表現します。これらの新しいタイプを使用するには、管理クラスを書き直す必要があります。これは、 aidl_interfaceパッケージ (下記) を作成する前に行うことができます。

  3. モジュールの名前、その依存関係、および必要なその他の情報を含むaidl_interfaceパッケージを (上記のように) 作成します。 (構造化だけでなく) 安定させるには、バージョン管理も必要です。詳細については、 「バージョニング インターフェイス」を参照してください。