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

AIDL バックエンド

AIDL バックエンドはスタブコード生成のターゲットです。AIDL ファイルは、常に特定の言語とランタイムで使用します。コンテキストに応じて、異なる AIDL バックエンドを使用する必要があります。

AIDL には次のバックエンドがあります。

バックエンド 言語 API サーフェス ビルドシステム
Java Java SDK / SystemApi(安定版*) すべて
NDK C++ libbinder_ndk(安定版*) aidl_interface
CPP C++ libbinder(不安定) すべて
  • これらの API サーフェスは安定版ですが、サービス管理用などの API の多くは内部プラットフォーム用に予約されているため、アプリでは使用できません。アプリで AIDL を使用する方法について詳しくは、デベロッパー向けドキュメントをご覧ください。

NDK バックエンドは Android 10 の新機能です。

ビルドシステム

バックエンドに応じて、AIDL をスタブコードにコンパイルする方法は 2 つあります。ビルドシステムについて詳しくは、Soong モジュールのリファレンスをご覧ください。

コア ビルドシステム

cc_ または java_ Android.bp モジュール(あるいは同等の Android.mk モジュール)では、.aidl ファイルをソースファイルとして指定できます。この場合、AIDL の Java / CPP バックエンドが使用され(NDK バックエンドは使用しない)、対応する AIDL ファイルを使用するクラスがモジュールに自動的に追加されます。aidl: グループでは、これらのモジュールにオプション(モジュール内の AIDL ファイルに対するルートパスをビルドシステムに伝える local_include_dirs など)を指定できます。詳しくは、Soong モジュールのリファレンスでビルドシステムに関するドキュメントをご覧ください。

aidl_interface

安定版の AIDL をご覧ください。このビルドシステムで使用する型は、構造化する必要があります。そのために AIDL で直接表現します。つまり、カスタム Parcelable は使用できません。

aidl コンパイラは、型のリファレンス実装と見なすことができます。インターフェースを作成するときに aidl --lang=<backend> ... を呼び出すと、作成されたインターフェース ファイルを確認できます。aidl_interface モジュールを使用する場合は、out/soong/.intermediates/<path to module>/ でも出力を見ることができます。

Java / AIDL 型 C++ 型 NDK 型
ブール値 ブール値 ブール値
byte int8_t int8_t
char char16_t char16_t
int int32_t int32_t
long int64_t int64_t
float float float
double double double
String android::String16 std::string
android.os.Parcelable android::Parcelable N/A
IBinder android::IBinder ndk::SpAIBinder
T[] std::vector<T> std::vector<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1
List<T> std::vector<T>2 N/A
FileDescriptor android::base::unique_fd N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor
interface type (T) android::sp<T> std::shared_ptr<T>
parcelable type (T) T T
union type (T)3 T T

1. Android 12 以降では、互換性の理由から、バイト配列は int8_t ではなく uint8_t を使用します。

2. C++ バックエンドは、List<String>List<IBinder> のみをサポートしています。すべてのバックエンドで機能することから、通常は T[] などの配列型を使用することをおすすめします。

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

方向(in/out/inout)

関数に引数のこの型を指定するときは、inout、または inout として指定できます。これは、IPC 呼び出しでどの方向に情報を渡すかを制御します。in はデフォルトの方向であり、データが呼び出し元から呼び出し先に渡されることを示します。out は、データが呼び出し先から呼び出し元に渡されることを意味します。inout は、上記 2 つの組み合わせです。ただし、引数指定子 inout は使用しないことをおすすめします。バージョニングされたインターフェースと古い呼び出し先で inout を使用する場合、呼び出し元のみに存在する追加フィールドは、デフォルト値にリセットされます。

UTF8 / UTF16

C++ バックエンドでは、文字列を utf-8 と utf-16 のどちらにするかを選択できます。AIDL で文字列を @utf8InCpp String として宣言すると、自動的に utf-8 に変換されます。 NDK バックエンドは常に utf-8 文字列を使用します。utf8InCpp アノテーションの詳細については、AIDL でのアノテーションをご覧ください。

null 可能性

Java で null にできる型は、@nullable でアノテーションを付けて、C++ / NDK に null 値を公開できます。デフォルトでは、ネイティブ サーバーは null 値を拒否します。 例外は、常に 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 では生成されません。

デフォルト値

構造化された 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 インターフェースで、組み込まれているエラーの種類で対応できない追加のエラー値が必要な場合は、サービス固有の特別な組み込みエラーを使用して、ユーザーが定義するサービス固有のエラー値を含めることができます。これらのサービス固有のエラーは通常、const int または int に基づく enum として AIDL インターフェースで定義されたもので、Binder では解析されません。

Java では、エラーは android.os.RemoteException などの例外にマッピングされます。サービス固有の例外の場合、Java ではユーザー定義のエラーとともに android.os.ServiceSpecificException が使用されます。

Android のネイティブ コードでは例外を使用しません。CPP バックエンドでは android::binder::Status を使用し、NDK バックエンドでは ndk::ScopedAStatus を使用します。AIDL によって生成されたすべてのメソッドは、これらのいずれかを返すことでメソッドのステータスを表します。サービス固有のエラーの場合、返される Status または ScopedAStatus で、EX_SERVICE_SPECIFIC とユーザー定義のエラーが使用されます。

組み込まれているエラーの種類は、次のファイルで確認できます。

バックエンド 定義
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h

各種のバックエンドの使用

以下の手順は、Android プラットフォーム コード固有です。これらの例では、定義型 my.package.IFoo を使用します。

型のインポート

定義型が interface、parcelable、union のいずれでも、Java にインポートできます。

    import my.package.IFoo;

CPP バックエンドにインポートする場合:

    #include <my/package/IFoo.h>

NDK バックエンドにインポートする場合(aidl 名前空間を追加):

    #include <aidl/my/package/IFoo.h>

ネストされた型は 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;
    }

サービスの登録と取得

通常、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")));

バインダーをホストするサービスが終了したときに通知が届くようにリクエストできます。これは、コールバック プロキシのリークの回避や、エラーの復旧に役立ちます。 これらの呼び出しは、バインダー プロキシ オブジェクトに対して行います。

Java では、android.os.IBinder::linkToDeath を使用します。CPP バックエンドでは、android::IBinder::linkToDeath を使用します。NDK バックエンドでは、AIBinder_linkToDeath を使用します。

サービスのバグレポートとデバッグ用 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) {...}

C++ バックエンド内:

    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;

インターフェース記述子の動的な取得

インターフェース記述子は、インターフェースのタイプを特定します。デバッグする場合や不明なバインダーがある場合に使用すると便利です。

Java では、次のようなコードを使用してインターフェース記述子を取得できます。

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

CPP バックエンドの場合:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

NDK バックエンドはこの機能をサポートしていません。

インターフェース記述子の静的な取得

@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

列挙型の範囲

ネイティブ バックエンドでは、列挙型が取り得る値を反復処理できます。コードサイズを考慮した結果、現時点では Java ではサポートされていません。

AIDL で定義された列挙型 MyEnum の場合、CPP バックエンドでは ::android::enum_range<MyEnum>() を使用して、NDK バックエンドでは ::ndk::enum_range<MyEnum>() を使用して反復処理が行われます。

スレッドの管理

プロセス内の libbinder のすべてのインスタンスが、1 つのスレッドプールを維持します。ほとんどのユースケースで、この 1 つのスレッドプールをすべてのバックエンドで共有する必要があります。唯一の例外は、ベンダーコードで libbinder のコピーをもう 1 回読み込んで /dev/vndbinder にアクセスする場合です。これは別のバインダノードにあるため、スレッドプールは共有されません。

Java バックエンドの場合、スレッドプールについて増大するのはサイズだけになります(すでに開始しているため)。

    BinderInternal.setMaxThreads(<new larger value>);

C++ バックエンドの場合は次の操作が可能です。

    // 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();