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 11 以降では、versions
はaidl_api/name
の下でフリーズされます。フリーズされたバージョンのインターフェースが存在しない場合、versions を指定する必要はありません。また、互換性チェックは行われません。stability
: このインターフェースの安定性の保証を示すフラグ(オプション)。 現在のところ、"vintf"
にのみ対応しています。設定されていない場合、このコンパイル コンテキスト内の安定したインターフェースに対応します(ここで読み込まれるインターフェースは、system.img など、一緒にコンパイルされたものでのみ使用できます)。"vintf"
に設定されている場合、安定性の保証(そのインターフェースを使用する限り、安定性が保証される)に対応します。gen_trace
: トレースをオンまたはオフにするフラグ(オプション)。デフォルトはfalse
です。host_supported
:true
に設定すると、生成されたライブラリがホスト環境で利用できるようになるフラグ(オプション)。unstable
: このインターフェースは安定版にする必要がないことを指定するためのフラグ(オプション)。true
に設定した場合、ビルドシステムは、インターフェース用の API ダンプを作成せず、それを更新する必要もありません。backend.<type>.enabled
: これらのフラグは、AIDL コンパイラがコードを生成する各バックエンドを切り替えます。現在、3 つのバックエンド(java
、cpp
、ndk
)がサポートされています。バックエンドはデフォルトではすべて有効になっています。特定のバックエンドが不要な場合は、明示的に無効にする必要があります。backend.<type>.apex_available
: 生成されたスタブ ライブラリが使用可能な APEX 名のリスト。backend.[cpp|java].gen_log
: トランザクションに関する情報を収集するための追加コードを生成するかどうかを制御するフラグ(オプション)。backend.[cpp|java].vndk.enabled
: このインターフェースを VNDK の一部にするフラグ(オプション)。デフォルトはfalse
です。backend.java.platform_apis
: プラットフォームからの非公開 API に対して Java スタブ ライブラリをビルドするかどうかを制御するフラグ(オプション)。stability
が"vintf"
に設定されている場合、この値は"true"
に設定する必要があります。backend.java.sdk_version
: Java スタブ ライブラリがビルドされる SDK のバージョンを指定するフラグ(オプション)。デフォルト値は"system_current"
です。backend.java.platform_apis
が true の場合、この値は設定しないでください。backend.java.platform_apis
: 生成されたライブラリを SDK ではなくプラットフォーム API に対してビルドする必要がある場合に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;
}
デフォルトでは boolean
、char
、float
、double
、byte
、int
、long
、String
が現在サポートされていますが、必須ではありません。
スタブ ライブラリの使用
モジュールにスタブ ライブラリを依存関係として追加した後で、それらをファイルに含めることができます。ビルドシステムのスタブ ライブラリの例を次に示します(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
プロパティが指定されると、フリーズされたバージョン間と、Top of Trunk(ToT)と最新のフリーズされたバージョン間で、ビルドシステムによる互換性チェックが行われます。
また、ToT バージョンの API 定義を管理することも必要です。API が更新されるたびに、foo-update-api を実行し、ToT バージョンの API 定義が含まれている aidl_api/name/current
を更新します。
インターフェースの安定性を維持するために、オーナーは以下を行うことができます。
- 新しいメソッド(または新しいシリアル番号が明示的に定義されたメソッド)をインターフェースの最後に追加する
- 新しい要素をパーセルの最後に追加する(要素ごとにデフォルトの追加が必要)
- 新しい定数値を追加する
- 新しい列挙子を追加する(Android 11 の場合)
その他のアクションは許可されません。また、オーナー以外がインターフェースを変更することもできません(できるとすると、オーナーが行った変更との間で競合が生じるおそれがあります)。
実行時に、古いサーバーで新しいメソッドを呼び出そうとすると、新しいクライアントが自動的に UNKNOWN_TRANSACTION
を取得します。これを処理するための戦略については、バージョンをクエリするとデフォルトを使用するをご覧ください。新しいフィールドが Parcelable に追加されると、古いクライアントとサーバーはそれらを削除します。新しいクライアントとサーバーが古い Parcelable を受信すると、新しいフィールドのデフォルト値が自動的に入力されます。つまり、Parcelable のすべての新しいフィールドに対してデフォルト値を指定する必要があります。同様に、クライアントとサーバーは必要に応じて、未確認の定数値と列挙子を拒否または無視する必要があります。今後さらに追加される可能性があるためです。
モジュールの命名規則
11 では、バージョンと有効にしたバックエンドの組み合わせごとに、スタブ ライブラリ モジュールが自動的に作成されます。リンクのために特定のスタブ ライブラリ モジュールを参照するには、aidl_interface
モジュールの名前ではなく、スタブ ライブラリ モジュールの名前(ifacename-version-backend)を使用します。
ifacename
:aidl_interface
モジュールの名前version
は次のいずれかです。- フリーズされたバージョンの場合は
Vversion-number
- 最新の(まだフリーズされていない)バージョンの場合は
unstable
- フリーズされたバージョンの場合は
backend
は次のいずれかです。- Java バックエンドの場合は
java
、 - C++ バックエンドの場合は
cpp
、 - NDK バックエンドの場合は
ndk
またはndk_platform
です。前者はアプリ向け、後者はプラットフォーム向けです。
- Java バックエンドの場合は
最新のフリーズされたバージョンの場合、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; }
}
これは、生成されたクラス(IFoo
、IFoo.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 インターフェースに変換するには、次の手順を実施します。
インターフェースのすべての依存関係を特定します。インターフェースが依存しているすべてのパッケージについて、パッケージが安定版 AIDL で定義されているかどうかを確認します。定義されていない場合は、パッケージを変換する必要があります。
インターフェース内のすべての Parcelable を stable Parcelable に変換します(インターフェース ファイル自体は変更されません)。この変換は、AIDL ファイルで構造を直接表現することによって行います。これらの新しいタイプを使用するには、管理クラスを書き直す必要があります。これは、
aidl_interface
パッケージの作成前に行うことができます(下記参照)。モジュールの名前、依存関係、その他の必要な情報を含む
aidl_interface
パッケージを作成します(前述の説明を参照)。構造化だけでなく安定化も実現するには、バージョニングを行う必要もあります。 詳細については、インターフェースのバージョニングをご覧ください。