Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

AIDL バックエンド

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> をサポートしています(TStringIBinderParcelFileDescriptor、Parcelable のいずれかです)。Android T(AOSP 試験運用版)以降では、T には配列以外の任意の非プリミティブ型(インターフェース型を含む)を指定できます。すべてのバックエンドで動作することから、AOSP では T[] などの配列型を使用することをおすすめします。

3. NDK バックエンドは List<T> をサポートしています(TStringParcelFileDescriptor、Parcelable のいずれかです)。Android T(AOSP 試験運用版)以降では、T には配列以外の任意の非プリミティブ型を指定できます。

4. Rust コードの型を渡す方法は、型が入力(引数)か出力(戻り値)かによって異なります。

5. union 型は Android 12 以降でサポートされています。

6. Android T(AOSP 試験運用版)以降では、固定サイズの配列がサポートされています。固定サイズの配列は、複数のディメンション(int[3][4] など)を持つことができます。Java バックエンド内では、固定サイズの配列は配列型として表されます。

方向(in/out/inout)

関数に引数の型を指定するときは、inout、または 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 エラー(StatusCodeExceptionCode)に変換してからユーザーに提供します。サービス固有のエラーの場合、返される 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();