ここで説明するベスト プラクティスは、特に AIDL を API の定義や API サーフェスとのやり取りに使用する場合に、AIDL インターフェースの柔軟性に注意を払いながら、AIDL インターフェースを効果的に開発するためのガイドとなります。
AIDL を使用すると、アプリ同士がバックグラウンド プロセスでやり取りする必要がある場合や、アプリがシステムとやり取りする必要がある場合に API を定義できます。AIDL を使用したアプリのプログラミング インターフェースの開発については、Android インターフェース定義言語(AIDL)を参照してください。実際の AIDL の例については、HAL 用の AIDL と安定版の AIDL を参照してください。
バージョニング
後方互換性のある AIDL API のスナップショットは、それぞれが 1 つのバージョンに対応します。スナップショットを取るには、「m <module-name>-freeze-api
」を実行します。API のクライアントやサーバーがリリースされるたびに(メインライン トレインのリリースなど)、スナップショットを取って新しいバージョンを作成する必要があります。システムとベンダーの間の API の場合は、1 年ごとのプラットフォーム改訂とともに行う必要があります。
詳細と、可能な変更の種類については、バージョニング インターフェースを参照してください。
API 設計のガイドライン
全般
1. すべてを文書化する
- すべてのメソッドを、そのセマンティクス、引数、組み込み例外の使用、サービス固有の例外、戻り値に関して文書化します。
- すべてのインターフェースのセマンティクスを文書化します。
- 列挙型と定数のセマンティクス上の意味を文書化します。
- 実装者にとって不明確になりそうな点をすべて文書化します。
- 必要に応じて例を示します。
2. 表記
型にはアッパー キャメルケースを使い、メソッド、フィールド、引数にはローワー キャメルケースを使用します。たとえば、Parcelable 型には MyParcelable
を使い、引数には anArgument
を使います。頭字語は 1 つの単語と見なします(NFC
-> Nfc
)。
[-Wconst-name] 列挙値と定数は ENUM_VALUE
と CONSTANT_NAME
のようにします。
インターフェース
1. 命名
[-Winterface-name] インターフェース名は「IFoo
」のように「I
」から始めます。
2. ID ベースの「オブジェクト」を持つ大規模なインターフェースを作らない
特定の API に関連する呼び出しが多数ある場合には、サブインターフェースに分割することをおすすめします。そうすることによって、次のようなメリットが得られます。 - クライアント/サーバーのコードが理解しやすいものになる - オブジェクトのライフサイクルが単純になる - バインダが偽造不可能であることを生かせる
非推奨: ID ベースのオブジェクトを持つ単一の大規模なインターフェース
interface IManager {
int getFooId();
void beginFoo(int id); // clients in other processes can guess an ID
void opFoo(int id);
void recycleFoo(int id); // ownership not handled by type
}
推奨: 個別のサブインターフェース
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. 単方向メソッドと双方向メソッドを混在させない
[-Wmixed-oneway] 単方向メソッドと双方向メソッドを混在させないでください。スレッドモデルが複雑になり、クライアントからもサーバーからも理解しづらくなるためです。具体的には、特定のインターフェースのクライアント コードを読む際に、メソッドごとにブロックするかどうかを確認する必要があります。
4. ステータス コードを返さないようにする
すべての AIDL メソッドにはステータスを示す暗黙の戻りコードがあるため、メソッドから戻り値としてステータス コードを返さないようにします。ServiceSpecificException
または EX_SERVICE_SPECIFIC
を参照してください。慣例として、AIDL インターフェースでは、これらの値を定数として定義します。詳細については、AIDL バックエンドのエラー処理のセクションを参照してください。
5. 出力パラメータとして配列を使用するのは有害と見なされる
[-Wout-array] 通常、void foo(out String[] ret)
のように配列型の出力パラメータがあるメソッドは好ましくありません。出力配列のサイズは Java のクライアント側で宣言して割り当てる必要があり、サーバー側ではサイズを選択できないためです。これは、配列を再割り当てできないという Java の性質に起因するものです。代わりに String[] foo()
のような API の使用をおすすめします。
6. inout パラメータを使わない
[-Winout-parameter] in
パラメータでも out
パラメータのように見えるため、クライアント側で混乱が発生する可能性があります。
7. out/inout @nullable の非配列パラメータを使わない
[-Wout-nullable] Java バックエンドは他のバックエンドと違って @nullable
アノテーションに対応しないため、「out/inout @nullable T
」を使用するとバックエンドによって挙動が違ってくる可能性があります。たとえば、Java 以外のバックエンドでは out @nullable
パラメータを null に設定できますが(C++ であれば std::nullopt
として設定)、Java クライアントはこれを null として認識できません。
構造化 Parcelable
1. 使用する場面
送信するデータ型が複数ある場合には、構造化 Parcelable を使用します。
現在のデータ型が 1 つでも、将来は増やす必要があれば同様にします。たとえば、String username
は使用しないでください。次のような拡張可能な Parcelable を使用してください。
parcelable User {
String username;
}
そうすれば、将来、次のように拡張できます。
parcelable User {
String username;
int id;
}
2. デフォルトを明示的に指定する
[-Wexplicit-default, -Wenum-explicit-default] フィールドのデフォルトを明示的に指定します。
非構造化 Parcelable
1. 使用する場面
現在、非構造化 Parcelable は、Java では @JavaOnlyStableParcelable
で使用でき、NDK バックエンドでは @NdkOnlyStableParcelable
で使用できます。通常、これらは簡単に構造化できない古い既存の Parcelable です。
定数と列挙型
1. ビットフィールドでは定数フィールドを使う
ビットフィールドでは定数フィールドを使用するようにしてください(たとえば、インターフェース内の const int FOO = 3;
)。
2. 列挙型は閉集合にする
列挙型は閉集合にしてください。注: 列挙型の要素を追加できるのはインターフェースのオーナーだけです。このようなフィールドをベンダーや OEM が拡張する必要がある場合には、別の仕組みが必要です。可能な限り、ベンダー機能のアップストリームをおすすめします。ただし、カスタムのベンダー値が許される場合もあります。その場合でも、ベンダーはこれをバージョン管理する仕組み(AIDL 自身の場合もあるでしょう)を設ける必要があります。競合することがないようにしてください。また、この値をサードパーティ アプリに公開しないでください。
3. 「NUM_ELEMENTS」のような値を使わない
列挙型はバージョン管理されるので、値がいくつ存在するかを示す値を使わないようにしてください。この問題は、C++ では enum_range<>
を使用することで回避できます。Rust では enum_values()
を使用してください。Java には、まだ解決策がありません。
非推奨: 番号付きの値の使用
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. 冗長な接頭辞と接尾辞を使わない
[-Wredundant-name] 定数と列挙子に冗長または反復的な接頭辞と接頭辞を使わない
非推奨: 冗長な接頭辞の使用
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
推奨: 列挙子の直接的な命名
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] AIDL インターフェース メソッドの引数や戻り値として FileDescriptor
を使用しないことを強くおすすめします。特に AIDL を Java で実装する場合、注意深く取り扱わないとファイル記述子のリークが発生します。基本的に、受け取った FileDescriptor
は不要になったときに手動で閉じる必要があります。
ネイティブ バックエンドの場合、FileDescriptor
は自動で閉じることが可能な unique_fd
にマッピングされるため安全です。しかし、使用するバックエンド言語が何であっても、将来バックエンド言語を変更する際の支障とならないよう、FileDescriptor
を一切使用しないのが賢明です。
代わりに、自動的に閉じることが可能な ParcelFileDescriptor
を使用してください。
変数の単位
変数名に単位を入れることで、単位が明確に定義され、ドキュメントを参照しなくても把握できるようにしてください。
例
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
タイムスタンプの基準点を必ず示す
タイムスタンプ(実際はすべての単位)については、その単位と基準点を明示してください。
例
/**
* Time since device boot in milliseconds
*/
long timestampMs;
/**
* UTC time received from the NTP server in units of milliseconds
* since January 1, 1970
*/
long utcTimeMs;