AIDL バックエンドはスタブコード生成のターゲットです。AIDL ファイルは、常に特定の言語とランタイムで使用します。コンテキストに応じて、異なる AIDL バックエンドを使用する必要があります。
AIDL には次のバックエンドがあります。
バックエンド | 言語 | API サーフェス | ビルドシステム |
---|---|---|---|
Java | Java | SDK / SystemApi(安定版*) | すべて |
NDK | C++ | libbinder_ndk(安定版*) | aidl_interface |
CPP | C++ | libbinder(不安定) | すべて |
Rust | Rust | libbinder_rs(不安定) | aidl_interface |
- これらの API サーフェスは安定版ですが、サービス管理用などの API の多くは内部プラットフォーム用に予約されているため、アプリでは使用できません。アプリで AIDL を使用する方法について詳しくは、デベロッパー向けドキュメントをご覧ください。
- Rust バックエンドは Android 12 で導入されました。NDK バックエンドは Android 10 から利用できます。
- Rust クレートは
libbinder_ndk
の上に構築されています。APEX では、システム側の他の要素と同じようにバインダ クレートが使用されます。出荷時には、Rust 部分は APEX にバンドルされています。これは、システム パーティションのlibbinder_ndk.so
によって異なります。
ビルドシステム
バックエンドに応じて、AIDL をスタブコードにコンパイルする方法は 2 つあります。ビルドシステムについて詳しくは、Soong モジュールのリファレンスをご覧ください。
コア ビルドシステム
cc_
または java_
Android.bp モジュール(あるいは同等の Android.mk
モジュール)では、.aidl
ファイルをソースファイルとして指定できます。この場合、AIDL の Java / CPP バックエンドが使用され(NDK バックエンドは使用しない)、対応する AIDL ファイルを使用するクラスがモジュールに自動的に追加されます。aidl:
グループでは、これらのモジュールにオプション(モジュール内の AIDL ファイルに対するルートパスをビルドシステムに伝える local_include_dirs
など)を指定できます。Rust バックエンドは Rust での使用のみを目的としています。rust_
モジュールの処理は、AIDL ファイルがソースファイルとして指定されないという点で異なります。代わりに、aidl_interface
モジュールは <aidl_interface name>-rust
と呼ばれる rustlib
を生成し、これに対してリンクできます。詳細については、Rust AIDL の例をご覧ください。
aidl_interface
安定版の AIDL をご覧ください。このビルドシステムで使用する型は、構造化する必要があります。そのために AIDL で直接表現します。つまり、カスタム Parcelable は使用できません。
型
aidl
コンパイラは、型のリファレンス実装と見なすことができます。インターフェースを作成するときに aidl --lang=<backend> ...
を呼び出して、生成されるインターフェース ファイルを確認します。aidl_interface
モジュールを使用する場合、out/soong/.intermediates/<path to module>/
で出力を表示できます。
Java / AIDL 型 | C++ 型 | NDK 型 | Rust 型 |
---|---|---|---|
ブール値 | ブール値 | ブール値 | ブール値 |
byte | int8_t | int8_t | i8 |
char | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
float | float | float | f32 |
double | double | double | f64 |
String | android::String16 | std::string | String |
android.os.Parcelable | android::Parcelable | N/A | N/A |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | In: &T Out: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 | In: &[u8] Out: Vec<u8> |
List<T> | std::vector<T>2 | std::vector<T>3 | In: &[T]4 Out: Vec<T> |
FileDescriptor | android::base::unique_fd | N/A | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
interface type (T) | android::sp<T> | std::shared_ptr<T> | binder::Strong |
parcelable type (T) | T | T | T |
union type (T)5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. Android 12 以降では、互換性の理由から、バイト配列は int8_t ではなく uint8_t を使用します。
2. C++ バックエンドは List<T>
をサポートしています(T
は String
、IBinder
、ParcelFileDescriptor
、Parcelable のいずれかです)。Android T(AOSP 試験運用版)以降では、T
には配列以外の任意の非プリミティブ型(インターフェース型を含む)を指定できます。すべてのバックエンドで動作することから、AOSP では T[]
などの配列型を使用することをおすすめします。
3. NDK バックエンドは List<T>
をサポートしています(T
は String
、ParcelFileDescriptor
、Parcelable のいずれかです)。Android T(AOSP 試験運用版)以降では、T
には配列以外の任意の非プリミティブ型を指定できます。
4. Rust コードの型を渡す方法は、型が入力(引数)か出力(戻り値)かによって異なります。
5. union 型は Android 12 以降でサポートされています。
6. Android T(AOSP 試験運用版)以降では、固定サイズの配列がサポートされています。固定サイズの配列は、複数のディメンション(int[3][4]
など)を持つことができます。Java バックエンド内では、固定サイズの配列は配列型として表されます。
方向(in/out/inout)
関数に引数の型を指定するときは、in
、out
、または inout
として指定できます。これは、IPC 呼び出しでどの方向に情報を渡すかを制御します。in
はデフォルトの方向であり、データが呼び出し元から呼び出し先に渡されることを示します。out
は、データが呼び出し先から呼び出し元に渡されることを意味します。inout
は、上記 2 つの組み合わせです。ただし、引数指定子 inout
は使用しないことをおすすめします。バージョニングされたインターフェースと古い呼び出し先で inout
を使用する場合、呼び出し元のみに存在する追加フィールドは、デフォルト値にリセットされます。Rust では、通常の inout
型は &mut Vec<T>
を受け取り、リストの inout
型は &mut Vec<T>
を受け取ります。
UTF8 / UTF16
CPP バックエンドでは、文字列を utf-8 と utf-16 のどちらにするかを選択できます。AIDL で文字列を @utf8InCpp String
として宣言すると、自動的に utf-8 に変換されます。
NDK バックエンドと Rust バックエンドでは常に utf-8 文字列が使用されます。utf8InCpp
アノテーションの詳細については、AIDL でのアノテーションをご覧ください。
null 可能性
Java バックエンドで null にできる型は @nullable
でアノテーションを付けると、CPP バックエンドと NDK バックエンドに null 値を公開できます。Rust バックエンドでは、これらの @nullable
型は Option<T>
として公開されます。デフォルトでは、ネイティブ サーバーは null 値を拒否します。例外は、NDK 読み取りと CPP / NDK 書き込みで常に null にできる interface
型と IBinder
型です。nullable
アノテーションの詳細については、AIDL でのアノテーションをご覧ください。
カスタム Parcelable
コア ビルドシステムの C++ バックエンドと Java バックエンドでは、ターゲット バックエンド(C++ または Java)に手動で実装される Parcelable を宣言できます。
package my.package;
parcelable Foo;
C++ ヘッダー宣言を使用した場合は次のようになります。
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
この Parcelable は AIDL ファイルで型として使用できますが、これは AIDL では生成されません。
Rust はカスタム Parcelable をサポートしていません。
デフォルト値
構造化された Parcelable では、プリミティブ、String
、およびそれらの型の配列について、フィールドごとにデフォルト値を宣言できます。
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Java バックエンドで、フィールドにデフォルト値がない場合、プリミティブ型はゼロの値で初期化され、非プリミティブ型は null
で初期化されます。
その他のバックエンドでは、フィールドのデフォルト値が未定義の場合、フィールドはデフォルトの初期値で初期化されます。たとえば、C++ バックエンドでは、String
フィールドは空の文字列で初期化され、List<T>
フィールドは空の vector<T>
で初期化されます。@nullable
フィールドは null 値フィールドで初期化されます。
エラー処理
Android OS には、エラーの報告時に使用できるエラーの種類が組み込まれています。これらは Binder によって使用され、Binder インターフェースを実装するすべてのサービスで使用できます。使用方法は AIDL の定義に詳しく記述されており、ユーザー定義のステータスや戻り値の型は必要ありません。
AIDL 関数でエラーが報告された場合、関数は出力パラメータを初期化、変更することはできません。具体的には、エラーがトランザクション自体の処理中ではなく、パーセリングの解除中に発生した場合に、出力パラメータが変更されます。一般的に、AIDL 関数からエラーを受け取った場合は、すべての inout
パラメータと out
パラメータ、戻り値(一部のバックエンドでは out
パラメータのように動作)は制限なしの状態とみなされます。
AIDL インターフェースで、組み込まれているエラーの種類で対応できない追加のエラー値が必要な場合は、サービス固有の特別な組み込みエラーを使用して、ユーザーが定義するサービス固有のエラー値を含めることができます。これらのサービス固有のエラーは通常、const int
または int
に基づく enum
として AIDL インターフェースで定義されたもので、Binder では解析されません。
Java では、エラーは android.os.RemoteException
などの例外にマッピングされます。サービス固有の例外の場合、Java ではユーザー定義のエラーとともに android.os.ServiceSpecificException
が使用されます。
Android のネイティブ コードでは例外を使用しません。CPP バックエンドでは android::binder::Status
を使用し、NDK バックエンドでは ndk::ScopedAStatus
を使用します。AIDL によって生成されたすべてのメソッドは、これらのいずれかを返すことでメソッドのステータスを表します。Rust バックエンドは、NDK と同じ例外コード値を使用しますが、ネイティブの Rust エラー(StatusCode
、ExceptionCode
)に変換してからユーザーに提供します。サービス固有のエラーの場合、返される Status
または ScopedAStatus
で、EX_SERVICE_SPECIFIC
とユーザー定義のエラーが使用されます。
組み込まれているエラーの種類は、次のファイルで確認できます。
バックエンド | 定義 |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
各種のバックエンドの使用
以下の手順は、Android プラットフォーム コード固有です。これらの例では、定義型 my.package.IFoo
を使用します。Rust バックエンドの使用方法については、Android Rust パターン ページの Rust AIDL の例をご覧ください。
型のインポート
定義型が interface、parcelable、union のいずれでも、Java にインポートできます。
import my.package.IFoo;
CPP バックエンドにインポートする場合:
#include <my/package/IFoo.h>
NDK バックエンドにインポートする場合(aidl
名前空間を追加):
#include <aidl/my/package/IFoo.h>
Rust バックエンドの場合:
use my_package::aidl::my::package::IFoo;
ネストされた型は Java でインポートできますが、CPP / NDK バックエンドでは、そのルートの型のヘッダーを含める必要があります。たとえば、my/package/IFoo.aidl
で定義されているネストされた型 Bar
をインポートする場合(IFoo
がファイルのルートの型)、CPP バックエンドが対象であれば <my/package/IFoo.h>
を、NDK バックエンドが対象であれば <aidl/my/package/IFoo.h>
を含めなければなりません。
サービスの実装
サービスを実装するには、ネイティブ スタブ クラスから継承する必要があります。このクラスでは、バインダ ドライバからコマンドを読み取り、実装するメソッドを実行します。次のような AIDL ファイルがあるとします。
package my.package;
interface IFoo {
int doFoo();
}
Java では、次のクラスから拡張する必要があります。
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
CPP バックエンドの場合:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
NDK バックエンドの場合(aidl
名前空間を追加):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
Rust バックエンドの場合:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
サービスの登録と取得
通常、Android プラットフォームのサービスは servicemanager
プロセスに登録されます。以下の API に加えて、サービスをチェックする API もあります(サービスが利用できない場合はすぐに戻ります)。
詳細については、対応する servicemanager
インターフェースを確認してください。これらの操作は、Android プラットフォームに対してコンパイルする場合にのみ実行できます。
Java の場合:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
CPP バックエンドの場合:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// getting
status_t err = getService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
NDK バックエンドの場合(aidl
名前空間を追加):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));
Rust バックエンドの場合:
use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
サービス終了にリンクする
バインダーをホストするサービスが終了したときに通知が届くようにリクエストできます。これは、コールバック プロキシのリークの回避や、エラーの復旧に役立ちます。 これらの呼び出しは、バインダ プロキシ オブジェクトに対して行います。
- Java では、
android.os.IBinder::linkToDeath
を使用します。 - CPP バックエンドでは、
android::IBinder::linkToDeath
を使用します。 - NDK バックエンドでは、
AIBinder_linkToDeath
を使用します。 - Rust バックエンドでは、
DeathRecipient
オブジェクトを作成し、my_binder.link_to_death(&mut my_death_recipient)
を呼び出します。なお、コールバックはDeathRecipient
が所有しているため、通知を受け取るには、このオブジェクトを維持する必要があります。
サービスのバグレポートとデバッグ用 API
バグレポートを(たとえば、adb bugreport
で)実行すると、システム全体からさまざまな問題のデバッグに役立つ情報が収集されます。AIDL サービスの場合、バグレポートは、サービス マネージャーに登録されているすべてのサービスでバイナリの dumpsys
を使用し、サービスの情報をバグレポートに出力します。また、コマンドラインで dumpsys
を使用し、dumpsys SERVICE [ARGS]
でサービスの情報を取得することもできます。C++ バックエンドと Java のバックエンドで、addService
に引数を追加すると、情報を出力するサービスの順番を指定できます。デバッグ中に dumpsys --pid SERVICE
を使用してサービスの PID を取得することもできます。
サービスにカスタム出力を追加するには、サーバー オブジェクトで、AIDL ファイルで定義されている他の IPC メソッドを実装するなどして、dump
メソッドをオーバーライドできます。この場合は、ダンプをアプリ権限 android.permission.DUMP
に制限するか、特定の UID に制限する必要があります。
Java バックエンドの場合:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
CPP バックエンドの場合:
status_t dump(int, const android::android::Vector<android::String16>&) override;
NDK バックエンドの場合:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Rust バックエンドでインターフェースを実装する際は、デフォルトの値を使用するのではなく、次のように指定します。
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
インターフェース記述子の動的な取得
インターフェース記述子は、インターフェースのタイプを特定します。デバッグする場合や不明なバインダーがある場合に使用すると便利です。
Java では、次のようなコードを使用してインターフェース記述子を取得できます。
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
CPP バックエンドの場合:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
NDK バックエンドと Rust バックエンドはこの機能をサポートしていません。
インターフェース記述子の静的な取得
@VintfStability
サービスを登録する場合などに、インターフェース記述子の内容を静的に確認したいことがあります。Java では、次のようなコードを追加して記述子を取得できます。
import my.package.IFoo;
... IFoo.DESCRIPTOR
CPP バックエンドの場合:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
NDK バックエンドの場合(aidl
名前空間を追加):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Rust バックエンドの場合:
aidl::my::package::BnFoo::get_descriptor()
列挙型の範囲
ネイティブ バックエンドでは、列挙型が取り得る値を反復処理できます。コードサイズを考慮した結果、現時点では Java ではサポートされていません。
AIDL で定義された列挙型 MyEnum
の反復処理は、次のように行われます。
CPP バックエンドの場合:
::android::enum_range<MyEnum>()
NDK バックエンドの場合:
::ndk::enum_range<MyEnum>()
Rust バックエンドの場合:
MyEnum::enum_range()
スレッドの管理
プロセス内の libbinder
のすべてのインスタンスが、1 つのスレッドプールを維持します。ほとんどのユースケースで、この 1 つのスレッドプールをすべてのバックエンドで共有する必要があります。唯一の例外は、ベンダーコードで libbinder
のコピーをもう 1 回読み込んで /dev/vndbinder
にアクセスする場合です。これは別のバインダノードにあるため、スレッドプールは共有されません。
Java バックエンドの場合、スレッドプールについて増大するのはサイズだけになります(すでに開始しているため)。
BinderInternal.setMaxThreads(<new larger value>);
CPP バックエンドの場合は次の操作が可能です。
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
NDK バックエンドでは次のようになります。
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
Rust バックエンドの場合:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();