AIDL バックエンドは、スタブ コード生成のターゲットです。 AIDL ファイルを使用する場合は、常に特定の言語と特定のランタイムで使用します。コンテキストに応じて、異なる AIDL バックエンドを使用する必要があります。
AIDL には次のバックエンドがあります。
バックエンド | 言語 | API サーフェス | ビルドシステム |
---|---|---|---|
ジャワ | ジャワ | SDK/SystemApi (安定版*) | 全て |
NDK | C++ | libbinder_ndk (安定版*) | aidl_interface |
CPP | C++ | libbinder (不安定) | 全て |
さび | さび | libbinder_rs (不安定) | aidl_interface |
- これらの API サーフェスは安定していますが、サービス管理用の 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 ファイルへのルート パスをビルド システムに伝えるlocal_include_dirs
などのオプションは、これらのモジュールのaidl:
グループの下で指定できます。 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 タイプ | さびの種類 |
---|---|---|---|
ブール値 | ブール | ブール | ブール |
バイト | int8_t | int8_t | i8 |
チャー | char16_t | char16_t | u16 |
整数 | int32_t | int32_t | i32 |
長いです | int64_t | int64_t | i64 |
浮く | 浮く | 浮く | f32 |
ダブル | ダブル | ダブル | f64 |
弦 | アンドロイド::文字列16 | std::文字列 | 弦 |
android.os.Parcelable | android::パーセル可能 | なし | なし |
IBinder | アンドロイド::IBinder | ndk::SpAIBinder | バインダー::SpIBinder |
T[] | std::vector<T> | std::vector<T> | 中: &[T] アウト: Vec<T> |
バイト[] | std::vector<uint8_t> | std::vector<int8_t> 1 | 中: &[u8] Out: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | 中: &[T] 4 アウト: Vec<T> |
ファイル記述子 | android::base::unique_fd | なし | バインダー::パーセル::ParcelFileDescriptor |
パーセル ファイル記述子 | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | バインダー::パーセル::ParcelFileDescriptor |
インターフェイス タイプ (T) | アンドロイド::sp<T> | std::shared_ptr<T> | バインダー::強い |
パーセルタイプ(T) | T | T | T |
ユニオンタイプ(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 13 以降では、 T
は配列を除く任意の非プリミティブ型 (インターフェース型を含む) にすることができます。 AOSP では、すべてのバックエンドで機能するため、 T[]
などの配列型を使用することをお勧めします。
3. NDK バックエンドはList<T>
をサポートしますT
はString
、 ParcelFileDescriptor
または parcelable のいずれかです。 Android 13 以降では、 T
は配列以外の非プリミティブ型にすることができます。
4. タイプが入力 (引数) であるか出力 (戻り値) であるかによって、型が Rust コードに異なる方法で渡されます。
5. Union 型は、Android 12 以降でサポートされています。
6. Android 13 以降では、固定サイズの配列がサポートされています。固定サイズの配列は複数の次元を持つことができます (例: int[3][4]
)。 Java バックエンドでは、固定サイズの配列は配列型として表されます。
方向性 (in/out/inout)
関数に引数の型を指定する場合、 in
、 out
、またはinout
として指定できます。これは、IPC 呼び出しで情報が渡される方向を制御します。 in
はデフォルトの方向であり、データが呼び出し元から呼び出し先に渡されることを示します。 out
は、データが呼び出し先から呼び出し元に渡されることを意味します。 inout
は、これらの両方の組み合わせです。ただし、Android チームは、引数指定子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 アノテーションの詳細については、 utf8InCpp
のアノテーションを参照してください。
Null可能性
Java バックエンドで null になる可能性のある型に@nullable
のアノテーションを付けて、null 値を CPP および NDK バックエンドに公開できます。 Rust バックエンドでは、これらの@nullable
型はOption<T>
として公開されます。デフォルトでは、ネイティブ サーバーは null 値を拒否します。これに対する唯一の例外はinterface
とIBinder
の型で、NDK の読み取りと CPP/NDK の書き込みでは常に null になる可能性があります。 nullable
アノテーションの詳細については、 AIDLのアノテーションを参照してください。
カスタムパーセルブル
コア ビルド システムの C++ および Java バックエンドでは、ターゲット バックエンド (C++ または Java) で手動で実装されるパーセルブルを宣言できます。
package my.package;
parcelable Foo;
または C++ ヘッダー宣言を使用:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
次に、この parcelable を AIDL ファイルのタイプとして使用できますが、AIDL によって生成されません。
Rust はカスタム 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 には、サービスがエラーを報告するときに使用する組み込みのエラー タイプが用意されています。これらはバインダーによって使用され、バインダー インターフェイスを実装する任意のサービスで使用できます。それらの使用法は AIDL 定義で十分に文書化されており、ユーザー定義のステータスや戻り値の型は必要ありません。
エラーのある出力パラメータ
AIDL 関数がエラーを報告すると、関数は出力パラメーターを初期化または変更しない場合があります。具体的には、トランザクション自体の処理中にエラーが発生するのではなく、パーセル解除中にエラーが発生した場合、出力パラメーターが変更される可能性があります。一般に、AIDL 関数からエラーが発生した場合、すべてのinout
およびout
パラメーターと戻り値 (一部のバックエンドではout
パラメーターのように機能します) は、不定状態にあると見なす必要があります。
どのエラー値を使用するか
組み込みのエラー値の多くは、任意の AIDL インターフェースで使用できますが、一部は特別な方法で処理されます。たとえば、 EX_UNSUPPORTED_OPERATION
とEX_ILLEGAL_ARGUMENT
は、エラー状態を説明する場合に使用しても問題ありませんが、 EX_TRANSACTION_FAILED
は、基盤となるインフラストラクチャによって特別に扱われるため、使用してはなりません。これらの組み込み値の詳細については、バックエンド固有の定義を確認してください。
AIDL インターフェースが、組み込みのエラー タイプでカバーされない追加のエラー値を必要とする場合、ユーザーが定義したサービス固有のエラー値を含めることができる特別なサービス固有の組み込みエラーを使用できます。 .これらのサービス固有のエラーは通常、AIDL インターフェースでconst int
またはint
に基づくenum
型として定義され、バインダーによって解析されません。
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
を使用します。
組み込みのエラー タイプは、次のファイルにあります。
バックエンド | 意味 |
---|---|
ジャワ | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
さび | android/binder_status.h |
さまざまなバックエンドの使用
これらの手順は、Android プラットフォーム コードに固有のものです。これらの例では、定義済みの型my.package.IFoo
を使用しています。 Rust バックエンドの使用方法については、 Android Rust PatternsページのRust AIDL の例を参照してください。
タイプのインポート
定義された型がインターフェース、パーセルブル、ユニオンのいずれであっても、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
<my/package/IFoo.h>
定義されたネストされたタイプBar
をインポートする場合 ( IFoo
はファイルのルート タイプです)、CPP バックエンド (または<aidl/my/package/IFoo.h>
NDK バックエンドの場合)。
サービスの実装
サービスを実装するには、ネイティブ スタブ クラスから継承する必要があります。このクラスは、バインダー ドライバーからコマンドを読み取り、実装したメソッドを実行します。次のような 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);
// return if service is started now
myService = IFoo.Stub.asInterface(ServiceManager.checkService("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);
// return if service is started now
status_t err = checkService<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");
// return if service is started now
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_checkService("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 で利用可能になります。 PID (またはプロセス ID) は、トランザクションを送信しているプロセスの Linux プロセス ID を参照します。 UID (またはユーザー ID) は、Linux ユーザー ID を指します。一方向呼び出しを受信する場合、呼び出し元の PID は 0 です。バインダー トランザクション コンテキストの外にある場合、これらの関数は現在のプロセスの PID と UID を返します。
Java バックエンドでは:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
CPP バックエンドで:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
NDK バックエンドで:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Rust バックエンドでは、インターフェースを実装するときに、(デフォルトにするのではなく) 以下を指定します。
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
サービスのバグ レポートとデバッグ 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 の別のコピーをロードして/dev/vndbinder
libbinder
対話する場合です。これは別のバインダー ノード上にあるため、スレッドプールは共有されません。
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();
予約名
C++、Java、および Rust では、キーワードとして、または言語固有の使用のためにいくつかの名前が予約されています。 AIDL は言語規則に基づく制限を適用しませんが、予約名と一致するフィールド名または型名を使用すると、C++ または Java のコンパイルが失敗する可能性があります。 Rust の場合、フィールドまたは型は「生の識別子」構文を使用して名前が変更され、 r#
プレフィックスを使用してアクセスできます。
非人間工学的なバインディングや完全なコンパイルの失敗を回避するために、可能であれば AIDL 定義で予約済みの名前を使用しないことをお勧めします。
AIDL 定義に予約済みの名前がある場合は、プロトコルの互換性を保ちながらフィールドの名前を安全に変更できます。ビルドを続行するにはコードを更新する必要があるかもしれませんが、既にビルドされたプログラムは引き続き相互運用できます。
避けるべき名前: * C++ キーワード* Java キーワード* Rust キーワード