安定版 AIDL

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

  • インターフェースは、ビルドシステムで aidl_interfaces を使用して定義されています。
  • インターフェースには、構造化データのみを含めることができます。目的のタイプを表す Parcelable は、AIDL 定義に基づいて自動的に作成され、マーシャリングとマーシャリング解除が自動的に行われます。
  • インターフェースは、stable(下位互換性のある)として宣言できます。その場合、API は AIDL インターフェースの横のファイルで追跡およびバージョニングされます。

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

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

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: 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 インターフェース モジュールのオプションのリスト。インポートした AIDL インターフェースで定義された AIDL のタイプは、import ステートメントでアクセスできます。
  • versions: api_dir の下でフリーズされている以前のバージョンのインターフェース。Android R 以降では、versionsaidl_api/name の下でフリーズされます。フリーズされたバージョンのインターフェースが存在しない場合、versions を指定する必要はありません。また、互換性チェックは行われません。
  • stability: このインターフェースの安定性の保証を示すオプションのフラグ。現在のところ、"vintf" にのみ対応しています。設定されていない場合、このコンパイル コンテキスト内の安定したインターフェースに対応します(ここで読み込まれるインターフェースは、system.img など、一緒にコンパイルされたものでのみ使用できます)。"vintf" に設定されている場合、安定性の保証(そのインターフェースを使用する限り、安定性が保証される)に対応します。
  • backend.<type>.enabled: これらのフラグは、AIDL コンパイラがコードを生成する各バックエンドを切り替えます。現在、3 つのバックエンド(javacppndk)がサポートされています。バックエンドはデフォルトではすべて有効になっています。特定のバックエンドが不要な場合は、明示的に無効にする必要があります。
  • backend.<type>.apex_available: 生成されたスタブ ライブラリが使用可能な APEX 名のリスト。
  • backend.java.platform_apis: プラットフォームからの非公開 API に対して Java スタブ ライブラリをビルドするかどうかを制御するオプションのフラグ。stability"vintf" に設定されている場合、この値は "true" に設定する必要があります。
  • backend.java.sdk_version: Java スタブ ライブラリがビルドされる SDK のバージョンを指定するオプションのフラグ。デフォルトは "system_current" です。backend.java.platform_apis が true の場合、この値は設定しないでください。

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

AIDL ファイルの書き込み

安定版 AIDL のインターフェースは、従来のインターフェースと似ていますが、構造化されていない Parcelable は使用できません(安定していないため)。安定版 AIDL の主な違いは、Parcelable の定義方法です。以前は、Parcelable は前方宣言されていました。安定版 AIDL では、Parcelable のフィールドと変数は明示的に定義されています。

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

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

デフォルトでは booleancharfloatdoublebyteintlongString が現在サポートされていますが、必須ではありません。

スタブ ライブラリの使用

モジュールにスタブ ライブラリを依存関係として追加した後で、それらをファイルに含めることができます。ビルドシステムのスタブ ライブラリの例を次に示します(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"],
        ...
    }
    

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
    

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

foo という名前のモジュールを宣言すると、モジュールの API の管理に使用できるターゲットもビルドシステムに作成されます。ビルド時に、Android のバージョンに応じて、次のバージョンのインターフェース用の新しい API 定義が foo-freeze-api によって api_dir または aidl_api/name に追加されます。また、このビルドによって versions プロパティが更新され、追加のバージョンが反映されます。versions プロパティが指定されると、フリーズされたバージョン同士、および ToT と最新のフリーズされたバージョンの間で、ビルドシステムによる互換性チェックが行われます。

インターフェースの安定性を維持するために、オーナーは以下を行うことができます。

  • 新しいメソッド(または新しいシリアル番号が明示的に定義されたメソッド)をインターフェースの最後に追加する
  • 新しい要素をパーセルの最後に追加する(要素ごとにデフォルトの追加が必要)
  • 新しい定数値を追加する
  • 新しい列挙子を追加する(Android R)

その他の操作は許可されません。また、オーナー以外がインターフェースを変更することもできません(そうしないと、オーナーが行った変更との間で競合が生じる恐れがあります)。

ランタイム時に、古いサーバーで新しいメソッドを呼び出そうとすると、新しいクライアントが自動的に UNKNOWN_TRANSACTION を取得します。これを処理するための戦略については、バージョンをクエリするデフォルトを使用するをご覧ください。新しいフィールドが Parcelable に追加されると、古いクライアントやサーバーはそれらを削除します。新しいクライアントやサーバーが古い Parcelable を受信すると、新しいフィールドのデフォルト値が自動的に入力されます。つまり、Parcelable のすべての新しいフィールドに対してデフォルト値を指定する必要があります。同様に、クライアントやサーバーは必要に応じて、未確認の定数値や列挙子を拒否または無視する必要があります。今後さらに追加される可能性があるためです。

モジュールの命名規則

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

  • ifacename: aidl_interface モジュールの名前
  • version は次のいずれかです。
    • フリーズされたバージョンの場合は Vversion-number
    • 最新の(まだフリーズされていない)バージョンの場合は unstable
  • backend は次のいずれかです。
    • Java バックエンドの場合は java
    • C++ バックエンドの場合は cpp
    • NDK バックエンドの場合は ndk または ndk_platform です。前者はアプリ向け、後者はプラットフォーム向けです。

最新のフリーズされたバージョンの場合、Java ターゲット以外はバージョン フィールドを省略します。 つまり、module-Vlatest-frozen-version-(cpp|ndk|ndk_platform) は生成されません。

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

  • バージョン 1 がベース
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • バージョン 2(最新の安定バージョン)がベース
    • foo-(java|cpp|ndk|ndk_platform)
    • foo-V2-java(内容は foo-java と同じ)
  • ToT バージョンがベース
    • foo-unstable-(java|cpp|ndk|ndk_platform)

ほとんどの場合、出力ファイルの名前はモジュール名と同じです。ただし、ToT バージョンの C++ または NDK モジュールの場合、出力ファイルの名前はモジュール名と異なります。

たとえば、foo-unstable-cpp の出力ファイルの名前は、以下に示すように、foo-unstable-cpp.so ではなく foo-V3-cpp.so です。

  • バージョン 1 がベース: foo-V1-(cpp|ndk|ndk_platform).so
  • バージョン 2 がベース: foo-V2-(cpp|ndk|ndk_platform).so
  • ToT バージョンがベース: foo-V3-(cpp|ndk|ndk_platform).so

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

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
    }
    

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
    }
    

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
    }
    

Java 言語の場合、リモート側で getInterfaceVersion() を次のように実装する必要があります。

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

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

古いインターフェースの使用

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

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

C++ の例:

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

    // once per an interface in a process
    IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));

    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 インターフェースで指定する必要はありません。リモート側で実装されることが保証されているメソッドは、デフォルトの impl クラスでオーバーライドする必要はありません(それらのメソッドが AIDL インターフェースの記述内にあれば、リモート側で確実にビルドされるため)。

既存の AIDL の構造化または安定版 AIDL への変換

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

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

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

  3. モジュールの名前、依存関係、その他の必要な情報を含む aidl_interface パッケージを作成します(前述の説明を参照)。構造化だけでなく安定化も実現するには、バージョニングを行う必要もあります。詳しくは、バージョニング インターフェースをご覧ください。