AIDL バックエンドはスタブコード生成のターゲットです。AIDL ファイルは、常に特定の言語とランタイムで使用します。コンテキストに応じて、異なる AIDL バックエンドを使用する必要があります。
以下の表で、API サーフェスの安定性とは、この API サーフェスに対し、コードを system.img
libbinder.so
バイナリから独立して提供できる方法でコンパイルできる機能のことを指します。
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
このビルドシステムで使用する型は、構造化する必要があります。構造化するには、Parcelable にフィールドが直接含まれるようにして、ターゲット言語で直接定義されるタイプの宣言にしないようにする必要があります。構造化 AIDL を安定版 AIDL に適合させる方法については、構造化 AIDL と安定版 AIDL の違いをご覧ください。
型
aidl
コンパイラは、型のリファレンス実装と見なすことができます。インターフェースを作成するときに aidl --lang=<backend> ...
を呼び出して、生成されるインターフェース ファイルを確認します。aidl_interface
モジュールを使用する場合、out/soong/.intermediates/<path to module>/
で出力を確認できます。
Java / AIDL 型 | C++ 型 | NDK 型 | Rust 型 |
---|---|---|---|
ブール値 | ブール値 | ブール値 | ブール値 |
バイト8 | 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>7 | 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 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 バックエンド内では、固定サイズの配列は配列型として表されます。
7. バインダ SharedRefBase
オブジェクトをインスタンス化するには、SharedRefBase::make\<My\>(... args ...)
を使用します。この関数は、バインダが別のプロセスによって所有されている場合に備えて、内部でも管理される std::shared_ptr\<T\>
オブジェクトを作成します。他の方法でオブジェクトを作成すると、所有権が二重になります。
8. Java / AIDL 型 byte[]
もご覧ください。
方向(in / out / inout)
関数に引数の型を指定するときは、in
、out
、または inout
として指定できます。これは、IPC 呼び出しでどの方向に情報を渡すかを制御します。in
はデフォルトの方向であり、データが呼び出し元から呼び出し先に渡されることを示します。out
は、データが呼び出し先から呼び出し元に渡されることを意味します。inout
は、上記 2 つの組み合わせです。ただし、引数指定子 inout
は使用しないことをおすすめします。バージョニングされたインターフェースと古い呼び出し先で inout
を使用する場合、呼び出し元のみに存在する追加フィールドは、デフォルト値にリセットされます。Rust では、通常の inout
型は &mut Vec<T>
を受け取り、リストの inout
型は &mut Vec<T>
を受け取ります。
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF8 / UTF16
CPP バックエンドでは、文字列を utf-8 と utf-16 のどちらにするかを選択できます。AIDL で文字列を @utf8InCpp String
として宣言すると、自動的に utf-8 に変換されます。
NDK バックエンドと Rust バックエンドでは常に utf-8 文字列が使用されます。utf8InCpp
アノテーションの詳細については、AIDL でのアノテーションをご覧ください。
null 可能性
null にできる型は @nullable
でアノテーションを付けることができます。nullable
アノテーションの詳細については、AIDL でのアノテーションをご覧ください。
カスタム Parcelable
カスタム Parcelable は、ターゲット バックエンドに手動で実装される Parcelable です。カスタム Paracelable は、変更不可能な既存のカスタム Paracelable に他の言語のサポートを追加する場合にのみ使用します。
カスタム Parcelable を宣言し AIDL で認識できるようにするには、次のように AIDL Paracelable を宣言します。
package my.pack.age;
parcelable Foo;
デフォルトでは、Java Paracelable が宣言されます。my.pack.age.Foo
は Parcelable
インターフェースを実装する Java クラスです。
AIDL でカスタム CPP バックエンド Parcelable を宣言する場合は、cpp_header
を使用します。
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
my/pack/age/Foo.h
での C++ 実装は次のようになります。
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
AIDL でカスタム NDK バックエンド Parcelable を宣言する場合は、ndk_header
を使用します。
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
android/pack/age/Foo.h
での NDK 実装は次のようになります。
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
Android 15 では、AIDL でカスタム Rust Parcelable を宣言するために rust_type
を使用します。
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
rust_crate/src/lib.rs
での Rust 実装は次のようになります。
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
この Parcelable は AIDL ファイルで型として使用できますが、これは AIDL では生成されません。カスタム CPP / NDK バックエンド Parcelable を union
で使用するには、<
と ==
の演算子を指定します。
デフォルト値
構造化された Parcelable では、プリミティブ、String
、およびそれらの型の配列について、フィールドごとにデフォルト値を宣言できます。
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Java バックエンドで、フィールドにデフォルト値がない場合、プリミティブ型はゼロの値で初期化され、非プリミティブ型は null
で初期化されます。
その他のバックエンドでは、フィールドのデフォルト値が未定義の場合、フィールドはデフォルトの初期値で初期化されます。たとえば、C++ バックエンドでは、String
フィールドは空の文字列で初期化され、List<T>
フィールドは空の vector<T>
で初期化されます。@nullable
フィールドは null 値フィールドで初期化されます。
共用体
AIDL 共用体はタグ付けされ、どのバックエンドでも機能は同様になります。デフォルトでは、最初のフィールドのデフォルト値に構築され、操作方法は言語ごとに異なります。
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Java の例
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
C++ と NDK の例
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Rust の例
Rust では、共用体は列挙型として実装され、明示的なゲッターとセッターはありません。
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
エラー処理
Android OS には、エラーの報告時に使用できるサービス用のエラーの種類が組み込まれています。これらはバインダによって使用され、バインダ インターフェースを実装するすべてのサービスで使用できます。使用方法は AIDL の定義に詳しく記述されており、ユーザー定義のステータスや戻り値の型は必要ありません。
出力パラメータにエラーがある場合
AIDL 関数でエラーが報告された場合、関数は出力パラメータを初期化、変更することはできません。具体的には、エラーがトランザクション自体の処理中ではなく、パーセリングの解除中に発生した場合に、出力パラメータが変更されます。一般的に、AIDL 関数からエラーを受け取った場合は、すべての inout
パラメータと out
パラメータ、戻り値(一部のバックエンドでは out
パラメータのように動作)は制限なしの状態とみなされます。
使用するエラー値
組み込みのエラー値の多くはどの AIDL インターフェースでも使用できますが、一部のエラー値は特別な方法で処理されます。たとえば、EX_UNSUPPORTED_OPERATION
と EX_ILLEGAL_ARGUMENT
はエラー条件の説明であれば使用できますが、EX_TRANSACTION_FAILED
は基盤となるインフラストラクチャで特別に処理されるため、使用しないでください。各組み込み値の詳細については、バックエンド固有の定義をご覧ください。
AIDL インターフェースで、組み込まれているエラーの種類で対応できない追加のエラー値が必要な場合は、サービス固有の特別な組み込みエラーを使用して、ユーザーが定義するサービス固有のエラー値を含めることができます。これらのサービス固有のエラーは通常、const int
または int
に基づく enum
として AIDL インターフェースで定義されたもので、バインダでは解析されません。
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(())
}
}
非同期 Rust を使用する場合:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async 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
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::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(ndk::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()
}
非同期 Rust バックエンドの場合(シングル スレッドのランタイムを使用):
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks will run on this thread.
std::future::pending().await
}
他のオプションとの大きな違いは、非同期 Rust とシングル スレッドのランタイムを使用する場合に join_thread_pool
を呼び出さないことです。これは、Tokio にスレッドを設定し、生成されたタスクをそこで実行できるようにする必要があるためです。この例では、メインスレッドがその目的を果たしています。tokio::spawn
を使って生成されたタスクは、すべてこのメインスレッドで実行されます。
非同期 Rust バックエンドの場合(マルチスレッドのランタイムを使用):
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
マルチスレッドの Tokio ランタイムでは、生成されたタスクはメインスレッドで実行されません。したがって、メインスレッドをアイドル状態にしないために join_thread_pool
をメインスレッドで呼び出すことは理にかなっています。非同期コンテキストから離れるには、block_in_place
で呼び出しをラップする必要があります。
サービス終了へのリンク
バインダをホストするサービスが終了したときに通知が届くようにリクエストできます。これは、コールバック プロキシのリークの回避や、エラーの復旧に役立ちます。 これらの呼び出しは、バインダ プロキシ オブジェクトに対して行います。
- 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 は WeakReference
には対応していますが、ネイティブ レイヤーでの弱いバインダー参照には対応していません。
CPP バックエンドでは、弱い型は wp<IFoo>
です。
NDK バックエンドでは、ScopedAIBinder_Weak
を使用します。
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
Rust バックエンドでは、WpIBinder
または Weak<IFoo>
を使用します。
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
インターフェース記述子の動的な取得
インターフェース記述子は、インターフェースのタイプを特定します。デバッグする場合や不明なバインダがある場合に使用すると便利です。
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_values()
スレッドの管理
プロセス内の libbinder
のすべてのインスタンスが、1 つのスレッドプールを維持します。ほとんどのユースケースでは、この 1 つのスレッドプールをすべてのバックエンドで共有する必要があります。唯一の例外は、ベンダーコードで libbinder
のコピーをもう一度読み込んで /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();
非同期 Rust バックエンドでは、binder と Tokio の 2 つのスレッドプールが必要になります。つまり、非同期 Rust を使用するアプリでは、特に join_thread_pool
を使用する場合、特別な配慮が必要になります。詳細については、サービスの登録に関するセクションをご覧ください。
予約済みの名前
C++、Java、Rust は一部の名前を予約して、キーワードとして使用したり、言語固有の使い方をしたりしています。AIDL では言語ルールに基づく制限は適用されませんが、C++ や Java では予約済みの名前と一致するフィールド名または型名を使用すると、コンパイルが失敗する可能性があります。Rust では「raw identifier」構文(接頭辞 r#
を使ってアクセス可能にする)を使用することで、こうしたフィールドや型の名前を変更できます。
使いづらいバインディングや完全なコンパイルの失敗を避けるために、AIDL の定義では予約済みの名前を使用しないことをおすすめします。
予約済みの名前がすでに AIDL の定義に含まれている場合は、プロトコルの互換性を維持したまま、フィールド名を安全に変更できます。ビルドを続行するためにコードの更新が必要になる場合がありますが、ビルド済みのプログラムは引き続き相互運用できます。
使用すべきでない名前: