安定版の AIDL

Android 10 では、安定版の Android インターフェース定義言語(AIDL)のサポートが追加されています。これは、AIDL インターフェースによって提供されるアプリケーション プログラミング インターフェース(API)とアプリケーション バイナリ インターフェース(ABI)をトラッキングするための新しい方法です。安定版 AIDL は AIDL とまったく同じように動作しますが、ビルドシステムがインターフェースの互換性のトラッキングを行い、開発者ができることに関しては制約があります。

  • インターフェースは、ビルドシステムで aidl_interfaces を使用して定義されています。
  • インターフェースには、構造化データのみを含めることができます。目的のタイプの Parcelable は、AIDL 定義に基づいて自動的に作成され、マーシャリングとマーシャリング解除が自動的に行われます。
  • インターフェースを stable(下位互換性あり)として宣言できます。その場合、API は AIDL インターフェースの横のファイルで追跡およびバージョニングされます。

構造化 AIDL と安定版 AIDL の違い

構造化 AIDL とは AIDL のみで定義されるタイプを指します。たとえば、Parcelable 宣言(カスタム Parcelable)は構造化 AIDL ではありません。AIDL で定義されたフィールドを持つ Parcelable は、構造化 Parcelable と呼ばれます。

安定版 AIDL では、Parcelable に加えられた変更に下位互換性があるかどうかをビルドシステムとコンパイラが把握できるように、構造化 AIDL が必要です。しかし、すべての構造化インターフェースが安定しているわけではありません。安定させるには、インターフェースは構造化タイプのみを使用し、下記のバージョニング機能も使用する必要があります。逆に、コア ビルドシステムを使ってビルドしたり、unstable:true を設定したりすると、インターフェースは安定しません。

AIDL インターフェースを定義する

aidl_interface の定義は次のようになります。

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name: AIDL インターフェースを一意に識別する AIDL インターフェース モジュールの名前。
  • srcs: インターフェースを構成する AIDL ソースファイルのリスト。パッケージ com.acme で定義された AIDL タイプ Foo のパスは、<base_path>/com/acme/Foo.aidl の形式にする必要があります。ここで、<base_path> には Android.bp があるディレクトリに関連する任意のディレクトリを指定できます。上記の例では、<base_path>srcs/aidl です。
  • local_include_dir: パッケージ名の冒頭のパス。これは前述の <base_path> に対応しています。
  • imports: 使用する aidl_interface モジュールのリスト。AIDL インターフェースのいずれかが、別の aidl_interface のインターフェース、または Parcelable を使用している場合は、その名前をここに入力します。名前自体を指定して最新バージョンを参照することも、バージョン サフィックスを付けた名前(-V1 など)を指定して特定のバージョンを参照することもできます。バージョンの指定は、Android 12 以降でサポートされています。
  • versions: api_dir の下でフリーズされている以前のバージョンのインターフェース。Android 11 以降では、versionsaidl_api/name の下でフリーズされます。フリーズされたバージョンのインターフェースが存在しない場合、versions は指定しないでください。また、互換性チェックは行われません。このフィールドは、Android 13 以降では versions_with_info に置き換えられています。
  • versions_with_info: タプルのリスト。各タプルには、フリーズされたバージョンの名前と、このバージョンの aidl_interface がインポートした他の aidl_interface モジュールのバージョン インポートのリストが含まれます。AIDL インターフェース IFACE のバージョン V の定義は、aidl_api/IFACE/V にあります。このフィールドは Android 13 で導入されたものであり、Android.bp で直接変更されることは想定されていません。このフィールドを追加、更新するには、*-update-api または *-freeze-api を呼び出します。また、ユーザーが *-update-api または *-freeze-api を呼び出すと、versions フィールドが versions_with_info に自動的に移行されます。
  • stability: このインターフェースの安定性の保証を示すフラグ(オプション)。これは "vintf" のみをサポートしています。stability が設定されていない場合、unstable が指定されていない限り、このインターフェースが後方互換であることをビルドシステムが確認します。設定されていない場合、このコンパイル コンテキスト内の安定したインターフェースに対応します(つまり、system.img と関連するパーティションにあるものなどのシステム関連のすべてのものか、vendor.img と関連するパーティションにあるものなどのベンダー関連のすべてのもののいずれか)。stability"vintf" に設定されている場合、安定性の保証(そのインターフェースを使用する限り、安定性が保証される)に対応します。
  • gen_trace: トレースをオンまたはオフにするフラグ(オプション)。Android 14 以降。cpp および java バックエンドのデフォルトは true
  • host_supported: true に設定すると、生成されたライブラリがホスト環境で利用できるようになるフラグ(オプション)。
  • unstable: このインターフェースは安定版にする必要がないことを指定するためのフラグ(オプション)。true に設定した場合、ビルドシステムは、インターフェース用の API ダンプを作成せず、それが更新されることも必要としません。
  • frozen: true のときは、以前のバージョンからインターフェースの変更がないことを示すフラグ(オプション)。これにより、ビルド時のチェックが容易になります。false に設定すると、インターフェースが開発中で、新しい変更があることを意味します。foo-freeze-api を実行すると、新しいバージョンが生成され、値が自動的に true に変更されます。Android 14 で導入されました。
  • backend.<type>.enabled: これらのフラグは、AIDL コンパイラがコードを生成する各バックエンドを切り替えます。Java、C++、NDK、Rust の 4 つのバックエンドがサポートされています。Java、C++、NDK のバックエンドはデフォルトで有効になっています。この 3 つのバックエンドのいずれかが不要な場合は、明示的に無効にする必要があります。Android 15 までは Rust はデフォルトで無効になっています。
  • backend.<type>.apex_available: 生成されたスタブ ライブラリが使用可能な APEX 名のリスト。
  • backend.[cpp|java].gen_log: トランザクションに関する情報を収集するための追加コードを生成するかどうかを制御するフラグ(オプション)。
  • backend.[cpp|java].vndk.enabled: このインターフェースを VNDK の一部にするフラグ(オプション)。デフォルトは false です。
  • backend.[cpp|ndk].additional_shared_libraries: Android 14 で導入されました。このフラグは、ネイティブ ライブラリに依存関係を追加します。このフラグは ndk_header または cpp_header で使用すると便利です。
  • backend.java.sdk_version: Java スタブ ライブラリがビルドされる SDK のバージョンを指定するフラグ(オプション)。デフォルト値は "system_current" です。backend.java.platform_apistrue の場合、この値は設定しないでください。
  • backend.java.platform_apis: 生成されたライブラリを SDK ではなくプラットフォーム API に対してビルドする必要がある場合に true に設定する必要があるフラグ(オプション)。

バージョンと有効になっているバックエンドの組み合わせごとに、スタブ ライブラリが作成されます。特定のバックエンドのスタブ ライブラリの特定のバージョンを参照する方法については、モジュールの命名規則をご覧ください。

AIDL ファイルを記述する

安定版 AIDL のインターフェースは、従来のインターフェースと似ていますが、構造化されていない Parcelable は使用できません(安定していないため。詳しくは構造化 AIDL と安定版 AIDL の違いを参照)。安定版 AIDL の主な違いは、Parcelable の定義方法です。以前は、Parcelable は前方宣言されていました。安定版(したがって構造化された)AIDL では、Parcelable のフィールドと変数は明示的に定義されています。

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

デフォルトでは booleancharfloatdoublebyteintlongString がサポートされていますが、必須ではありません。Android 12 では、ユーザー定義の列挙型のデフォルトもサポートされています。デフォルトが指定されていない場合、0 に類似した値か空の値が使用されます。デフォルト値のない列挙は、ゼロの列挙子がない場合でも 0 に初期化されます。

スタブ ライブラリを使用する

モジュールにスタブ ライブラリを依存関係として追加した後で、それらをファイルに含めることができます。ビルドシステムのスタブ ライブラリの例を次に示します(Android.mk は従来のモジュールの定義にも使用できます)。

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

C++ の例:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Java の例:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Rust の例:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

インターフェースのバージョニング

foo という名前のモジュールを宣言すると、モジュールの API の管理に使用できるターゲットもビルドシステムに作成されます。ビルド時に、Android のバージョンに応じて、新しい API 定義が foo-freeze-api によって api_dir または aidl_api/name に追加され、.hash ファイルが追加されます。どちらも、インターフェースの新たにフリーズされたバージョンを表します。foo-freeze-api によって、追加のバージョンとそのバージョンの imports を反映するように versions_with_info プロパティも更新されます。基本的に、versions_with_infoimportsimports フィールドからコピーされます。ただし、インポートについては最新の安定版が versions_with_infoimports に指定されていますが、明示的なバージョンはありません。versions_with_info プロパティが指定されると、フリーズされたバージョン間と、Top of Tree(ToT)と最新のフリーズされたバージョン間で、ビルドシステムによる互換性チェックが行われます。

また、ToT バージョンの API 定義を管理することも必要です。API が更新されるたびに、foo-update-api を実行し、ToT バージョンの API 定義が含まれている aidl_api/name/current を更新します。

インターフェースの安定性を維持するために、オーナーは以下を行うことができます。

  • 新しいメソッド(または新しいシリアル番号が明示的に定義されたメソッド)をインターフェースの最後に追加する
  • 新しい要素を Parcelable の最後に追加する(要素ごとにデフォルトの追加が必要)
  • 新しい定数値を追加する
  • 新しい列挙子を追加する(Android 11 の場合)
  • 新しいフィールドを union の最後に追加する(Android 12 の場合)

その他のアクションは許可されません。また、オーナー以外がインターフェースを変更することもできません(そうしないと、オーナーが行った変更との間で競合が生じるおそれがあります)。

すべてのインターフェースがリリースのためにフリーズされているかをテストするには、次の環境変数を設定してビルドします。

  • AIDL_FROZEN_REL=true m ... - ビルドで、owner: フィールドが指定されていないすべての安定版 AIDL インターフェースをフリーズする必要があります。
  • AIDL_FROZEN_OWNERS="aosp test" - ビルドで、owner: フィールドが「aosp」または「test」として指定されたすべての安定版 AIDL インターフェースをフリーズする必要があります。

インポートの安定性

インターフェースのフリーズ バージョンに対するインポートのバージョンの更新は、安定版 AIDL レイヤで下位互換性があります。ただし、これらを更新すると、以前のバージョンのインターフェースを使用しているすべてのサーバーとクライアントを更新する必要があり、異なるバージョンのタイプが混在すると一部のアプリが混乱する可能性があります。一般に、タイプのみのパッケージまたは共通パッケージでは、IPC トランザクションの不明なタイプを処理するコードがすでに作成されている必要があるため、これは安全です。

Android プラットフォームのコードでは、android.hardware.graphics.common が、このタイプのバージョン アップグレードの最たる例です。

バージョニングされたインターフェースを使用する

インターフェース メソッド

実行時に、古いサーバーで新しいメソッドを呼び出そうとすると、バックエンドに応じて、新しいクライアントでエラーか例外が発生します。

  • cpp バックエンドが ::android::UNKNOWN_TRANSACTION を取得します。
  • ndk バックエンドが STATUS_UNKNOWN_TRANSACTION を取得します。
  • java バックエンドが android.os.RemoteException を取得し、API が実装されていないことを示すメッセージが表示されます。

これを処理するための戦略については、バージョンをクエリするデフォルトを使用するをご覧ください。

Parcelable

新しいフィールドが Parcelable に追加されると、古いクライアントやサーバーはそれらを削除します。新しいクライアントやサーバーが古い Parcelable を受信すると、新しいフィールドのデフォルト値が自動的に入力されます。つまり、Parcelable 内のすべての新しいフィールドには、デフォルトを指定する必要があります。

フィールドが定義されているバージョンを実装していることを認識している場合を除き、クライアントは、サーバーが新しいフィールドを使用することを想定すべきではありません(バージョンをクエリするを参照)。

列挙型と定数

同様に、クライアントやサーバーは必要に応じて、未確認の定数値や列挙子を拒否または無視する必要があります。今後さらに追加される可能性があるためです。たとえば、サーバーは認識していない列挙子を受け取った場合に中止すべきではありません。サーバーは列挙子を無視するか、何かを返して、この実装でサポートされていないことをクライアントが認識できるようにする必要があります。

共用体

新しいフィールドで union を送信しようとする際、レシーバーが古くそのフィールドを認識できない場合は失敗します。実装では、新しいフィールドの union が認識されません。一方向のトランザクションの場合、このエラーは無視されます。それ以外の場合、エラーは BAD_VALUE(C++ または NDK バックエンドの場合)または IllegalArgumentException(Java バックエンドの場合)になります。このエラーは、クライアントが新しいフィールドに設定された union を古いサーバーに送信するか、古いクライアントが新しいサーバーから union を受信している場合に受信されます。

複数のバージョンを管理する

Android のリンカー名前空間では、特定の aidl インターフェースには 1 つのバージョンのみが許されます。これは、生成される aidl タイプが複数の定義を持たないようにするためです。C++ には、各シンボルに 1 つの定義のみという単一定義ルールがあります。

Android ビルドでは、1 つのモジュールが同じ aidl_interface ライブラリの異なるバージョンに依存していると、エラーとなります。直接依存している場合もあれば、依存関係の依存関係を通じて間接的に依存している場合もあります。これらのエラーには、aidl_interface ライブラリの競合しているバージョンの依存関係グラフが表示されます。すべての依存関係は、これらのライブラリの同じ(通常は最新の)バージョンを含むように修正する必要があります。

インターフェース ライブラリが多数のモジュールから使用されている場合、同じバージョンを使用する必要のあるライブラリとプロセスのグループに cc_defaultsjava_defaultsrust_defaults を作成することも解決に役立ちます。インターフェースの新しいバージョンを導入する際に、さきほどのデフォルトを更新すると、それらを使用するモジュールのすべても合わせて更新され、インターフェースの異なるバージョンが使用されることはなくなります。

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

aidl_interface モジュールで他の aidl_interface モジュールをインポートすると、特定のバージョンが共に使用されることを要求する新たな依存関係が生まれます。この状況で、同じプロセスでいっしょに使用されている複数の aidl_interface モジュールでインポートされている共通の aidl_interface モジュールが存在すると、管理が困難になります。

aidl_interfaces_defaults は、1 か所で更新できる aidl_interface の依存関係の最新バージョンの定義を 1 つだけにするために使用でき、その共通のインターフェースをインポートしようとするすべての aidl_interface から使用できます。

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

フラグベースの開発

開発中(フリーズされていない)のインターフェースは、下位互換性が保証されていないため、リリース デバイスでは使用できません。

AIDL は、最新のフリーズされていないバージョンを対象にコードを記述してもリリース デバイスで使用できるように、これらのフリーズされていないインターフェース ライブラリのランタイム フォールバックをサポートしています。クライアントの下位互換動作は既存の動作と同様で、フォールバックした場合にも、既存の動作に沿うように実装する必要があります。バージョニングされたインターフェースを使用するをご覧ください。

AIDL ビルドフラグ

この動作を制御するフラグは、build/release/build_flags.bzl で定義される RELEASE_AIDL_USE_UNFROZEN です。true の場合、実行時にはフリーズされていないバージョンのインターフェースが使用され、false の場合、フリーズされていないバージョンのライブラリは、すべて最新のフリーズ バージョンと同様に動作します。ローカルで開発する場合はこのフラグを true にオーバーライドできますが、リリース前には false に戻す必要があります。開発時は通常、このフラグを true に設定します。

互換性マトリックスとマニフェスト

ベンダー インターフェース オブジェクト(VINTF オブジェクト)は、ベンダー インターフェースの両側で期待されるバージョンと提供されるバージョンを定義します。

Cuttlefish 以外のほとんどのデバイスは、インターフェースがフリーズされた後の最新の互換性マトリックスのみを対象にするため、RELEASE_AIDL_USE_UNFROZEN に基づく AIDL ライブラリにおける違いはありません。

マトリックス

パートナー所有のインターフェースは、開発中にデバイスがターゲットとするデバイス固有の互換性マトリックスまたはプロダクト固有の互換性マトリックスに追加されます。そのため、フリーズされていない新しいバージョンのインターフェースが互換性マトリックスに追加された場合、RELEASE_AIDL_USE_UNFROZEN=false の場合用に以前のフリーズ バージョンを保持しておく必要があります。これは、RELEASE_AIDL_USE_UNFROZEN の設定ごとに異なる互換性マトリックス ファイルを使用するか、すべての設定で使用される単一の互換性マトリックス ファイルで両方のバージョンを許可することで対応できます。

たとえば、フリーズされていないバージョン 4 を追加する場合は、<version>3-4</version> を使用します。

バージョン 4 がフリーズされると、RELEASE_AIDL_USE_UNFROZENfalse の場合にフリーズ バージョン 4 が使用されるため、互換性マトリックスからバージョン 3 を削除できます。

マニフェスト

Android 15 では、RELEASE_AIDL_USE_UNFROZEN の値に基づいてビルド時にマニフェスト ファイルを変更するための libvintf に修正が行われました。

マニフェストとマニフェスト フラグメントでは、サービスがどのバージョンのインターフェースを実装するかを宣言します。インターフェースのフリーズされていない最新のバージョンを使用する場合、この新しいバージョンを反映するようにマニフェストを更新する必要があります。RELEASE_AIDL_USE_UNFROZEN=false の場合、マニフェストのエントリは、生成された AIDL ライブラリの変更を反映するように libvintf によって調整されます。フリーズされていないバージョン N から最後のフリーズされたバージョン N - 1 に修正されます。そのため、各サービスについて、複数のマニフェストやマニフェスト フラグメントを管理する必要はありません。

HAL クライアントの変更

HAL クライアント コードは、これまでサポートされてきたフリーズ バージョンそれぞれと下位互換性を保つ必要があります。RELEASE_AIDL_USE_UNFROZENfalse の場合、サービスは常に最新のフリーズ バージョンまたはそれ以前のフリーズ バージョンと同じように動作します(たとえば、フリーズされていない新しいメソッドを呼び出すと UNKNOWN_TRANSACTION が返されたり、新しい parcelable フィールドがデフォルト値を持っていたりします)。Android フレームワーク クライアントでは、追加された以前のバージョンとの下位互換性が要求されますが、ベンダー クライアントやパートナー所有インターフェースのクライアントにとっては新たなものです。

HAL 実装の変更

HAL 開発とフラグベース開発の最大の違いは、HAL 実装では、RELEASE_AIDL_USE_UNFROZENfalse の場合に動作するように、最新のフリーズ バージョンとの下位互換性が要求されることです。実装とデバイスコードで下位互換性を考慮することは新しい作業となります。バージョニングされたインターフェースを使用するをご覧ください。

下位互換性を考慮することは、クライアントとサーバー間および、フレームワーク コードとベンダーコード間では基本的に同じですが、同じソースコード(フリーズされていない現在のバージョン)を使用する 2 つのバージョンを効果的に実装しようとする際は、注意しなければならない微妙な違いがあります。

例: 3 つのフリーズ バージョンがあるインターフェースに、新しいメソッドが追加されました。クライアントとサーバーは新しいバージョン 4 ライブラリを使用するように更新されます。V4 ライブラリはフリーズされていないバージョンのインターフェースをベースにしているため、RELEASE_AIDL_USE_UNFROZENfalse の場合、最新のフリーズ バージョンであるバージョン 3 と同じように動作し、新しいメソッドを使用できないようにします。

インターフェースがフリーズされると、RELEASE_AIDL_USE_UNFROZEN のすべての値の場合でそのフリーズ バージョンが使用され、下位互換性を処理するコードは削除できます。

コールバックのメソッドを呼び出して UNKNOWN_TRANSACTION が返された場合は、適切に処理する必要があります。クライアントがリリース設定に基づいて 2 つの異なるバージョンのコールバックを実装している可能性があるため、クライアントが最新バージョンを送信するとは想定できず、新しいメソッドがこのように返すこともあります。これは、バージョニングされたインターフェースを使用するで説明している、安定した AIDL クライアントがサーバーとの下位互換性を確保する方法と同様です。

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

RELEASE_AIDL_USE_UNFROZENfalse の場合、既存の型(parcelableenumunion)の新しいフィールドが存在しないか、デフォルト値を含んでいる可能性があります。そのため、サービスが新しいフィールドの値を送信しようとすると、プロセスの途中で破棄されます。

フリーズされていないバージョンで追加された新しい型は、インターフェースを通して送受信できません。

RELEASE_AIDL_USE_UNFROZENfalse の場合、実装はどのクライアントからも新しいメソッドの呼び出しを受けません。

新しい列挙子は、導入されたバージョンでのみ使用し、以前のバージョンでは使用しないように注意してください。

通常は foo->getInterfaceVersion() を利用して、リモート インターフェースが使っているバージョンを確認します。しかし、フラグベースのバージョニングをサポートしている場合、2 つの異なるバージョンを実装することになるので、現在のインターフェースのバージョンを把握する必要があります。this->getInterfaceVersion()my_ver の他のメソッドなど、現在のオブジェクトのインターフェース バージョンを取得することで把握できます。詳しくはリモート オブジェクトのインターフェース バージョンをクエリするをご覧ください。

新しい VINTF 安定版インターフェース

新しい AIDL インターフェース パッケージが追加された場合、最新のフリーズ バージョンが存在しないので、RELEASE_AIDL_USE_UNFROZENfalse のときにフォールバックする動作はありません。これらのインターフェースは使用しないでください。RELEASE_AIDL_USE_UNFROZENfalse の場合、Service Manager はサービスにインターフェースの登録を許可せず、クライアントはそのインターフェースを見つけられません。

デバイスの makefile の RELEASE_AIDL_USE_UNFROZEN フラグの値に基づいて、条件付きでサービスを追加できます。

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

サービスがより大きなプロセスの一部であり、デバイスに条件付きで追加できない場合は、IServiceManager::isDeclared() でサービスが宣言されているかどうかを確認できます。宣言されていて登録に失敗した場合は、プロセスを中断します。宣言されていない場合は、登録に失敗すると想定されます。

開発ツールとしての Cuttlefish

毎年 VINTF がフリーズされた後、Cuttlefish のフレームワーク互換性マトリックス(FCM)の target-levelPRODUCT_SHIPPING_API_LEVEL を調整し、翌年リリースされるデバイスが反映されるようにしています。target-levelPRODUCT_SHIPPING_API_LEVEL を調整するのは、翌年のリリースに対する新たな要件を満たす、テスト済みのリリース予定デバイスがあることを確認するためです。

RELEASE_AIDL_USE_UNFROZENtrue の場合、Cuttlefish は将来の Android リリースの開発に使用されます。翌年の Android リリースの FCM レベルと PRODUCT_SHIPPING_API_LEVEL を対象とし、次のリリースのベンダー ソフトウェア要件(VSR)を満たす必要があります。

RELEASE_AIDL_USE_UNFROZENfalse の場合、Cuttlefish は以前の target-levelPRODUCT_SHIPPING_API_LEVEL でリリース デバイスを反映します。Android 14 以前のバージョンでは、この区別は、Git ブランチを分け、FCM target-level、出荷時の API レベル、次のリリースを対象とするその他のコードへの変更を反映しないことで行います。

モジュールの命名規則

Android 11 では、バージョンと有効にしたバックエンドの組み合わせごとに、スタブ ライブラリ モジュールが自動的に作成されます。リンクのために特定のスタブ ライブラリ モジュールを参照するには、aidl_interface モジュールの名前ではなく、スタブ ライブラリ モジュールの名前(ifacename-version-backend)を使用します。

  • ifacename: aidl_interface モジュールの名前
  • version は次のいずれかです。
    • フリーズされたバージョンの場合は Vversion-number
    • 最新の(まだフリーズされていない)バージョンの場合は Vlatest-frozen-version-number + 1
  • backend は次のいずれかです。
    • Java バックエンドの場合は java
    • C++ バックエンドの場合は cpp
    • NDK バックエンドの場合は ndk または ndk_platform です。Android 13 までは、前者はアプリ向け、後者はプラットフォーム向けです。Android 13 移行は、ndk のみを使用してください。
    • Rust バックエンドの場合は rust です。

foo という名前のモジュールが存在し、その最新バージョンが 2 であるとすると、そのモジュールは NDK と C++ の両方をサポートします。この場合、AIDL は以下のモジュールを生成します。

  • バージョン 1 がベース
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • バージョン 2(最新の安定バージョン)がベース
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • ToT バージョンがベース
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Android 11 と比較した場合:

  • 最新の安定版を参照する foo-backendfoo-V2-backend になります
  • ToT バージョンを参照する foo-unstable-backendfoo-V3-backend になります

出力ファイルの名前はモジュール名と常に同じです。

  • バージョン 1 がベース: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • バージョン 2 がベース: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • ToT バージョンがベース: foo-V3-(cpp|ndk|ndk_platform|rust).so

AIDL コンパイラは、unstable バージョン モジュールや、安定版 AIDL インターフェース用のバージョン管理されていないモジュールを作成しません。Android 12 以降、安定版 AIDL インターフェースから生成されたモジュール名には常にそのバージョンが含まれます。

新しいメタ インターフェース メソッド

Android 10 では、安定版 AIDL 用のメタ インターフェース メソッドがいくつか追加されています。

リモート オブジェクトのインターフェース バージョンをクエリする

クライアントは、リモート オブジェクトが実装しているインターフェースのバージョンとハッシュをクエリして、返された値をクライアントが使用しているインターフェースの値と比較できます。

cpp バックエンドを使用した例:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

ndk(および ndk_platform)バックエンドを使用した例:

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

java バックエンドを使用した例:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Java 言語の場合、リモート側で getInterfaceVersion()getInterfaceHash() を次のように実装する必要があります(コピーや貼り付けのミスを避けるために、IFoo ではなく super を使用します。javac 構成によっては、警告を無効にするためにアノテーション @SuppressWarnings("static") が必要になることがあります)。

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

これは、生成されたクラス(IFooIFoo.Stub など)がクライアントとサーバーの間で共有されるためです(たとえば、クラスはブート クラスパスに含めることができます)。クラスが共有されている場合、サーバーが古いバージョンのインターフェースでビルドされた可能性があるとしても、サーバーは最新バージョンのクラスにもリンクされます。このメタ インターフェースが共有クラスに実装されている場合、常に最新バージョンが返されます。ただし、上記のようにメソッドを実装すると、インターフェースのバージョン番号がサーバーのコードに埋め込まれます(IFoo.VERSION は参照時にインライン化される static final int であるため)。したがってこのメソッドは、サーバーのビルドに使用された正確なバージョンを返すことができます。

古いインターフェースを使用する

クライアントが新しいバージョンの AIDL インターフェースで更新され、サーバーが古い AIDL インターフェースを使用している可能性があります。そのような場合、古いインターフェースでメソッドを呼び出すと、UNKNOWN_TRANSACTION が返されます。

安定版 AIDL を使用すると、クライアントはより詳細な制御を行えます。クライアント側では、デフォルトの実装を AIDL インターフェースに設定できます。デフォルトの実装のメソッドは、リモート側で実装されていない場合にのみ呼び出されます(古いバージョンのインターフェースでビルドされているため)。デフォルトはグローバルで設定されるため、共有される可能性があるコンテキストからは使用しないでください。

Android 13 以降の C++ での例。

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Java の例:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process

foo.anAddedMethod(...);

すべてのメソッドのデフォルトの実装を AIDL インターフェースで指定する必要はありません。リモート側で実装されることが保証されているメソッドは、デフォルトの impl クラスでオーバーライドする必要はありません(それらのメソッドが AIDL インターフェースの記述内にあれば、リモート側で確実にビルドされるため)。

既存の AIDL を構造化された AIDL または安定版 AIDL に変換する

既存の AIDL インターフェースとそれを使用するコードがある場合、インターフェースを安定版 AIDL インターフェースに変換するには、次の手順を実施します。

  1. インターフェースのすべての依存関係を特定します。インターフェースが依存しているすべてのパッケージについて、パッケージが安定版 AIDL で定義されているかどうかを確認します。定義されていない場合は、パッケージを変換する必要があります。

  2. インターフェース内のすべての Parcelable を stable Parcelable に変換します(インターフェース ファイル自体は変更されません)。この変換は、AIDL ファイルで構造を直接表現することによって行います。これらの新しいタイプを使用するには、管理クラスを書き直す必要があります。これは、aidl_interface パッケージの作成前に行うことができます(下記参照)。

  3. モジュールの名前、依存関係、その他の必要な情報を含む aidl_interface パッケージを作成します(前述の説明を参照)。構造化だけでなく安定化も実現するには、バージョニングを行う必要もあります。 詳細については、インターフェースのバージョニングをご覧ください。