Android API ガイドライン

このページは、API 審査で API Council が適用する一般的な原則をデベロッパーが理解するためのガイドです。

API を記述する際にこれらのガイドラインに従うだけでなく、デベロッパーは API Lint ツールを実行する必要があります。このツールは、これらのルールの多くを、API に対して実行するチェックにエンコードします。

これは、その Lint ツールが従うルールのガイドと、そのツールで高い精度でコード化できないルールに関する一般的なアドバイスと考えることができます。

API Lint ツール

API Lint は Metalava 静的解析ツールに統合されており、CI の検証中に自動的に実行されます。m checkapi を使用してローカルのプラットフォーム チェックアウトから、または ./gradlew :path:to:project:checkApi を使用してローカルの AndroidX チェックアウトから手動で実行できます。

API ルール

Android プラットフォームと多くの Jetpack ライブラリは、このガイドラインが作成される前から存在しており、このページの後半で説明するポリシーは、Android エコシステムのニーズを満たすために継続的に進化しています。

そのため、既存の API の一部はガイドラインに準拠していない可能性があります。また、ガイドラインに厳密に準拠するよりも、新しい API が既存の API と一貫性を保つ方が、アプリ デベロッパーにとってユーザー エクスペリエンスが向上する場合もあります。

API に関する難しい質問で解決が必要なものや、更新が必要なガイドラインがある場合は、ご自身の判断で API Council にお問い合わせください。

API の基本

このカテゴリは、Android API のコア アスペクトに関連しています。

すべての API を実装しなければなりません

API のオーディエンス(一般公開、@SystemApi など)に関係なく、API サーフェスはすべて、API として統合または公開されるときに実装する必要があります。後日実装される API スタブを実装とマージしないでください。

実装のない API サーフェスには、次のような複数の問題があります。

  • 適切なサーフェスまたは完全なサーフェスが公開されている保証はありません。API がクライアントによってテストまたは使用されるまで、クライアントが機能を使用するために適切な API を持っていることを確認する方法はありません。
  • 実装のない API は、デベロッパー プレビューでテストできません。
  • 実装のない API は CTS でテストできません。

すべての API をテストする必要があります

これは、プラットフォームの CTS 要件、AndroidX ポリシー、および API を実装する必要があるという一般的な考え方に沿ったものです。

API サーフェスのテストは、API サーフェスが使用可能であり、想定されるユースケースに対応していることを保証する基本となります。存在のテストだけでは不十分です。API 自体の動作をテストする必要があります。

新しい API を追加する変更には、同じ CL または Gerrit トピックに対応するテストを含める必要があります。

API はテスト可能である必要があります。「アプリ デベロッパーは、あなたの API を使用するコードをどのようにテストしますか?」という質問に答えられるようにする必要があります。

すべての API を文書化する必要がある

ドキュメントは API の使いやすさの重要な要素です。API サーフェスの構文は明らかに見えるかもしれませんが、新しいクライアントは API の背後にあるセマンティクス、動作、コンテキストを理解できません。

生成されたすべての API はガイドラインに準拠している必要があります

ツールによって生成された API は、手書きのコードと同じ API ガイドラインに準拠する必要があります。

API の生成に推奨されないツール:

  • AutoValue: AutoValue の動作方法では、最終値クラスや最終ビルダーを実装する方法がないなど、さまざまな方法でガイドラインに違反しています。

コードのスタイル

このカテゴリは、デベロッパーが使用すべき一般的なコードスタイル、特に公開 API を記述する際に使用すべきコードスタイルに関連しています。

特に記載がない限り、標準的なコーディング規約に従う

Android のコーディング規約は、外部のコントリビューター向けにこちらで説明されています。

https://source.android.com/source/code-style.html

全体として、標準の Java と Kotlin のコーディング規則に従う傾向があります。

メソッド名では頭字語を大文字にしないでください

たとえば、メソッド名は runCTSTests ではなく runCtsTests にする必要があります。

名前の末尾に Impl を使用しない

これは実装の詳細を公開することになるため、避けるべきです。

クラス

このセクションでは、クラス、インターフェース、継承に関するルールについて説明します。

適切なベースクラスから新しいパブリック クラスを継承する

継承により、サブクラスで適切でない可能性のある API 要素が公開されます。たとえば、FrameLayout の新しい公開サブクラスは、FrameLayout に新しい動作と API 要素が追加されたものになります。継承された API がユースケースに適していない場合は、ツリーの上位のクラス(ViewGroupView など)から継承します。

基本クラスのメソッドをオーバーライドして UnsupportedOperationException をスローしたくなるかもしれませんが、使用している基本クラスを再検討してください。

基本コレクション クラスを使用する

コレクションを引数として受け取る場合でも、値として返す場合でも、常に特定の実装よりも基本クラスを優先します(ArrayList<Foo> ではなく List<Foo> を返すなど)。

API に適切な制約を表す基底クラスを使用します。たとえば、コレクションの順序が重要となる API には List を使用し、コレクションが一意の要素で構成される必要がある API には Set を使用します。

Kotlin では、不変コレクションを使用することをおすすめします。詳細については、コレクションの可変性をご覧ください。

抽象クラスとインターフェース

Java 8 では、デフォルトのインターフェース メソッドのサポートが追加されました。これにより、API 設計者はバイナリ互換性を維持しながら、インターフェースにメソッドを追加できます。プラットフォーム コードとすべての Jetpack ライブラリは、Java 8 以降をターゲットにする必要があります。

デフォルトの実装がステートレスの場合、API 設計者は抽象クラスよりもインターフェースを優先するべきです。つまり、デフォルトのインターフェース メソッドは、他のインターフェース メソッドの呼び出しとして実装できます。

デフォルトの実装でコンストラクタまたは内部状態が必要な場合は、抽象クラスを使用しなければなりません

どちらの場合も、API 設計者は、単一のメソッドを抽象化して、ラムダとしての使用を簡素化できます。

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

クラス名は拡張するものを反映している必要があります

たとえば、Service を拡張するクラスには、わかりやすくするために FooService という名前を付けます。

public class IntentHelper extends Service {}
public class IntentService extends Service {}

汎用接尾辞

ユーティリティ メソッドのコレクションに HelperUtil などの一般的なクラス名の接尾辞を使用することは避けてください。代わりに、メソッドを関連するクラスまたは Kotlin 拡張関数に直接配置します。

メソッドが複数のクラスをブリッジする場合は、その処理内容を説明する意味のある名前を包含クラスに付けます。

非常に限られたケースでは、Helper 接尾辞の使用が適切な場合があります。

  • デフォルトの動作の構成に使用されます
  • 既存の動作を新しいクラスに委任することが必要になる場合がある
  • 永続状態が必要になる場合がある
  • 通常は View が含まれます

たとえば、ツールチップのバックポートに View に関連付けられた状態の永続化と、バックポートをインストールするための View での複数のメソッドの呼び出しが必要な場合、TooltipHelper は許容されるクラス名になります。

IDL で生成されたコードをパブリック API として直接公開しない

IDL で生成されたコードは実装の詳細として保持します。これには、protobuf、ソケット、FlatBuffers、その他の Java 以外の NDK 以外の API サーフェスが含まれます。ただし、Android の IDL のほとんどは AIDL にあるため、このページでは AIDL に焦点を当てます。

生成された AIDL クラスは API スタイルガイドの要件を満たしておらず(オーバーロードを使用できないなど)、AIDL ツールは言語 API の互換性を維持するように明示的に設計されていないため、公開 API に埋め込むことはできません。

代わりに、AIDL インターフェースの上に公開 API レイヤを追加します(初期状態では浅いラッパーであっても)。

Binder インターフェース

Binder インターフェースが実装の詳細である場合、将来的に自由に変更できます。公開レイヤでは、必要な下位互換性を維持できます。たとえば、内部呼び出しに新しい引数を追加したり、バッチ処理やストリーミング、共有メモリの使用などによって IPC トラフィックを最適化したりする必要がある場合があります。AIDL インターフェースが公開 API でもある場合、これらの操作はできません。

たとえば、FooService を公開 API として直接公開しないでください。

// BAD: Public API generated from IFooService.aidl
public class IFooService {
   public void doFoo(String foo);
}

代わりに、Binder インターフェースをマネージャーまたは別のクラス内にラップします。

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo);
}

public IFooManager {
   public void doFoo(String foo) {
      mFooService.doFoo(foo);
   }
}

後でこの呼び出しに新しい引数が必要になった場合、内部インターフェースは最小限に抑えられ、便利なオーバーロードが公開 API に追加されます。ラッピング レイヤを使用して、実装の進化に伴う下位互換性の問題を処理できます。

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo, int flags);
}

public IFooManager {
   public void doFoo(String foo) {
      if (mAppTargetSdkLevel < 26) {
         useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
         mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
      } else {
         mFooService.doFoo(foo, 0);
      }
   }

   public void doFoo(String foo, int flags) {
      mFooService.doFoo(foo, flags);
   }
}

Android プラットフォームの一部ではない Binder インターフェース(アプリで使用するために Google Play 開発者サービスによってエクスポートされるサービス インターフェースなど)の場合、安定した公開済みのバージョン管理された IPC インターフェースの要件は、インターフェース自体を進化させるのがはるかに難しいことを意味します。ただし、他の API ガイドラインに合わせ、IPC インターフェースの新しいバージョンで同じ公開 API を使用しやすくするために、ラッパー レイヤを設けることは依然として有益です。

公開 API で未加工の Binder オブジェクトを使用しない

Binder オブジェクトは単独では意味がないため、公開 API で使用しないでください。一般的なユースケースの 1 つは、ID セマンティクスを持つ Binder または IBinder をトークンとして使用することです。生の Binder オブジェクトを使用する代わりに、ラッパー トークン クラスを使用します。

public final class IdentifiableObject {
  public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
  /**
   * @hide
   */
  public Binder getRawValue() {...}

  /**
   * @hide
   */
  public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}

public final class IdentifiableObject {
  public IdentifiableObjectToken getToken() {...}
}

マネージャー クラスは final にする必要があります

マネージャー クラスは final として宣言する必要があります。マネージャー クラスはシステム サービスと通信し、単一のインタラクション ポイントとなります。カスタマイズの必要がないため、final として宣言します。

CompletableFuture または Future を使用しない

java.util.concurrent.CompletableFuture には、将来の値の任意の変更を許可する大きな API サーフェスがあり、エラーが発生しやすいデフォルトがあります。

一方、java.util.concurrent.Future にはノンブロッキング リスニングがないため、非同期コードでの使用は困難です。

プラットフォーム コードKotlin と Java の両方で利用される低レベル ライブラリ API では、完了コールバック、Executor、API がキャンセルをサポートしている場合は CancellationSignal の組み合わせを優先します。

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Kotlin をターゲットにしている場合は、suspend 関数を使用してください。

suspend fun asyncLoadFoo(): Foo

Java 固有の統合ライブラリでは、Guava の ListenableFuture を使用できます。

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Optional を使用しない

Optional は一部の API サーフェスで利点がある可能性がありますが、既存の Android API サーフェス領域と一貫性がありません。@Nullable@NonNullnull の安全性を確保するためのツール支援を提供し、Kotlin はコンパイラ レベルで null 許容性コントラクトを適用するため、Optional は不要です。

省略可能なプリミティブの場合は、ペアの has メソッドと get メソッドを使用します。値が設定されていない場合(hasfalse を返す場合)、get メソッドは IllegalStateException をスローする必要があります。

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

インスタンス化できないクラスには private コンストラクタを使用する

Builder でのみ作成できるクラス、定数または静的メソッドのみを含むクラス、またはインスタンス化できないクラスには、デフォルトの引数なしコンストラクタを使用したインスタンス化を防ぐために、少なくとも 1 つのプライベート コンストラクタを含める必要があります。

public final class Log {
  // Not instantiable.
  private Log() {}
}

シングルトン

シングルトンにはテスト関連の次のような欠点があるため、使用は推奨されません。

  1. 構築はクラスによって管理され、フェイクの使用を防ぎます
  2. シングルトンの静的な性質により、テストを密閉できない
  3. これらの問題を回避するには、シングルトンの内部の詳細を把握するか、シングルトンのラッパーを作成する必要があります。

これらの問題に対処するために抽象ベースクラスに依存する 単一インスタンス パターンを優先します。

単一インスタンス

単一インスタンス クラスは、private または internal コンストラクタを持つ抽象ベースクラスを使用し、インスタンスを取得するための静的 getInstance() メソッドを提供します。getInstance() メソッドは、後続の呼び出しで同じオブジェクトを返す必要があります。

getInstance() が返すオブジェクトは、抽象基本クラスのプライベート実装である必要があります。

class Singleton private constructor(...) {
  companion object {
    private val _instance: Singleton by lazy { Singleton(...) }

    fun getInstance(): Singleton {
      return _instance
    }
  }
}
abstract class SingleInstance private constructor(...) {
  companion object {
    private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
    fun getInstance(): SingleInstance {
      return _instance
    }
  }
}

単一インスタンスは、シングルトンとは異なります。単一インスタンスでは、デベロッパーは SingleInstance のフェイク バージョンを作成し、独自の依存性注入フレームワークを使用して実装を管理できます。ラッパーを作成する必要はありません。また、ライブラリは -testing アーティファクトで独自のフェイクを提供できます。

リソースを解放するクラスは AutoCloseable を実装するべきです

closereleasedestroy などのメソッドでリソースを解放するクラスは、java.lang.AutoCloseable を実装して、デベロッパーが try-with-resources ブロックを使用する際にこれらのリソースを自動的にクリーンアップできるようにする必要があります。

android.* に新しい View サブクラスを導入しない

プラットフォームの公開 API(つまり android.*)で、android.view.View から直接的または間接的に継承する新しいクラスを導入しないでください。

Android の UI ツールキットは、Compose ファーストになりました。プラットフォームによって公開される新しい UI 機能は、Jetpack ライブラリのデベロッパーが Jetpack Compose と、必要に応じて View ベースの UI コンポーネントを実装するために使用できる下位レベルの API として公開されるべきです。ライブラリでこれらのコンポーネントを提供することで、プラットフォーム機能が利用できない場合にバックポート実装を行う機会が得られます。

フィールド

これらのルールは、クラスの公開フィールドに関するものです。

未加工のフィールドを公開しない

Java クラスはフィールドを直接公開すべきではありません。フィールドは非公開にし、final かどうかに関係なく、公開ゲッターとセッターを使用してのみアクセスできるようにする必要があります。

まれな例外として、フィールドの指定や取得の動作を強化する必要がない基本的なデータ構造があります。このような場合、フィールドには標準の変数命名規則(Point.xPoint.y など)を使用して名前を付ける必要があります。

Kotlin クラスはプロパティを公開できます。

公開フィールドは final としてマークする必要があります

未加工のフィールドは強く推奨されません(未加工のフィールドを公開しないを参照)。ただし、フィールドが公開フィールドとして公開されるまれな状況では、そのフィールドを final とマークします。

内部フィールドは公開しない

パブリック API で内部フィールド名を参照しないでください。

public int mFlags;

protected の代わりに public を使用する

@see protected ではなく public を使用する

定数

これらは公開定数に関するルールです。

フラグ定数は int 値または long 値と重複しないようにする

フラグは、いくつかの共用体値に結合できるビットを意味します。そうでない場合は、変数または定数 flag を呼び出さないでください。

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

公開フラグ定数の定義について詳しくは、ビットマスク フラグの @IntDef をご覧ください。

static final 定数には、すべて大文字でアンダースコアで区切られた命名規則を使用する必要があります

定数のすべての単語を大文字にし、複数の単語は _ で区切る必要があります。次に例を示します。

public static final int fooThing = 5
public static final int FOO_THING = 5

定数に標準の接頭辞を使用する

Android で使用される定数の多くは、フラグ、キー、アクションなどの標準的なものです。これらの定数には、標準の接頭辞を付けて、これらのものとして識別しやすくする必要があります。

たとえば、インテント エクストラは EXTRA_ で始める必要があります。インテント アクションは ACTION_ で始まる必要があります。Context.bindService() で使用される定数は BIND_ で始まる必要があります。

キー定数の名前とスコープ

文字列定数値は定数名自体と一致している必要があり、通常はパッケージまたはドメインにスコープ設定されます。次に例を示します。

public static final String FOO_THING = "foo"

名前が適切に付けられておらず、スコープも適切に設定されていません。代わりに、次のことを検討してください。

public static final String FOO_THING = "android.fooservice.FOO_THING"

スコープ付き文字列定数の android の接頭辞は、Android オープンソース プロジェクト用に予約されています。

インテント アクションとエクストラ、および Bundle エントリは、定義されているパッケージ名を使用して名前空間を設定する必要があります。

package android.foo.bar {
  public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
  public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}

protected の代わりに public を使用する

@see protected ではなく public を使用する

一貫した接頭辞を使用する

関連する定数はすべて同じ接頭辞で始まる必要があります。たとえば、フラグ値で使用する定数のセットは次のようになります。

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see 定数に標準の接頭辞を使用する

一貫性のあるリソース名を使用する

パブリック識別子、属性、値は、Java のパブリック フィールドと同様に、camelCase 命名規則(@id/accessibilityActionPageUp@attr/textAppearance など)を使用して命名する必要があります

公開 ID または属性に、アンダースコアで区切られた共通の接頭辞が含まれる場合があります。

  • config.xml@string/config_recentsComponentName などのプラットフォーム構成値
  • attrs.xml@attr/layout_marginStart などのレイアウト固有のビュー属性

公開テーマとスタイルは、Java のネストされたクラスと同様に、階層型の PascalCase 命名規則(@style/Theme.Material.Light.DarkActionBar@style/Widget.Material.SearchView.ActionBar など)に従う必要があります。

レイアウト リソースとドローアブル リソースは、公開 API として公開すべきではありません。ただし、公開する必要がある場合は、公開レイアウトとドローアブルは、アンダースコアの命名規則(layout/simple_list_item_1.xmldrawable/title_bar_tall.xml など)を使用して命名しなければなりません

定数が変更される可能性がある場合は動的にする

コンパイラは定数値をインライン化する可能性があるため、値を同じに保つことは API 契約の一部と見なされます。MIN_FOO または MAX_FOO 定数の値が将来変更される可能性がある場合は、代わりに動的メソッドにすることを検討してください。

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

コールバックの前方互換性を考慮する

古い API をターゲットとするアプリは、将来の API バージョンで定義された定数を認識しません。そのため、アプリに配信される定数は、アプリのターゲット API バージョンを考慮し、新しい定数を一貫した値にマッピングする必要があります。次のシナリオを考えてみます。

SDK のソースの例:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

targetSdkVersion="22" を含む架空のアプリ:

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

この場合、アプリは API レベル 22 の制約内で設計されており、2 つの状態しかありえないという(やや)妥当な想定に基づいていました。ただし、アプリが新しく追加された STATUS_FAILURE_RETRY を受け取った場合、これは成功と解釈されます。

定数を返すメソッドは、アプリが対象とする API レベルに合わせて出力を制約することで、このようなケースを安全に処理できます。

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

デベロッパーは、定数のリストが将来変更されるかどうかを予測できません。包括的な UNKNOWN 定数または UNSPECIFIED 定数を使用して API を定義すると、デベロッパーはアプリの作成時に公開された定数が網羅的であると想定します。この期待値を設定したくない場合は、API にキャッチオール定数を使用することが適切かどうかを再検討してください。

また、ライブラリはアプリとは別に独自の targetSdkVersion を指定できません。ライブラリ コードから targetSdkVersion の動作の変更を処理することは複雑でエラーが発生しやすくなります。

整数または文字列定数

値の Namespace がパッケージ外で拡張可能でない場合は、整数定数と @IntDef を使用します。名前空間が共有されている場合や、パッケージ外のコードで拡張できる場合は、文字列定数を使用します。

データクラス

データクラスは、変更不可能なプロパティのセットを表し、そのデータを操作するための小さく明確に定義されたユーティリティ関数のセットを提供します。

Kotlin コンパイラは、生成されたコードの言語 API またはバイナリ互換性を保証しないため、公開 Kotlin API で data class を使用しないでください。代わりに、必要な関数を手動で実装します。

インスタンス化

Java では、プロパティが少ない場合はコンストラクタを提供し、プロパティが多い場合は Builder パターンを使用する必要があります。

Kotlin では、プロパティの数に関係なく、データクラスはデフォルト引数を含むコンストラクタを提供する必要があります。Kotlin で定義されたデータクラスも、Java クライアントをターゲットとする場合はビルダーを提供することでメリットが得られる可能性があります。

変更とコピー

データを変更する必要がある場合は、コピー コンストラクタ(Java)を含む Builder クラスか、新しいオブジェクトを返す copy() メンバー関数(Kotlin)のいずれかを提供します。

Kotlin で copy() 関数を指定する場合、引数はクラスのコンストラクタと一致し、デフォルトはオブジェクトの現在の値を使用して入力する必要があります。

class Typography(
  val labelMedium: TextStyle = TypographyTokens.LabelMedium,
  val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
    fun copy(
      labelMedium: TextStyle = this.labelMedium,
      labelSmall: TextStyle = this.labelSmall
    ): Typography = Typography(
      labelMedium = labelMedium,
      labelSmall = labelSmall
    )
}

その他の動作

データクラスは equals()hashCode() の両方を実装し、これらのメソッドの実装ではすべてのプロパティを考慮する必要があります。

データクラスは、Kotlin のデータクラスの実装に一致する推奨形式(User(var1=Alex, var2=42) など)で toString() を実装できます。

メソッド

これらは、メソッドのさまざまな詳細(パラメータ、メソッド名、戻り値の型、アクセス指定子など)に関するルールです。

時間

これらのルールは、日付や期間などの時間に関するコンセプトを API でどのように表現すべきかを規定しています。

可能な限り java.time.* 型を使用する

java.time.Durationjava.time.Instant、その他多くの java.time.* 型は、脱糖を通じてすべてのプラットフォーム バージョンで利用可能であり、API パラメータまたは戻り値で時間を表す場合は、これらの型を使用することが推奨されます。

API ドメインが、意図された使用パターンでのオブジェクト割り当てがパフォーマンスに大きな影響を与えるようなドメインでない限り、java.time.Duration または java.time.Instant を受け入れるか返す API のバリアントのみを公開し、同じユースケースのプリミティブ バリアントは省略することが望ましい。

期間を表すメソッドには duration という名前を付ける

時間値が関連する時間の長さを表す場合は、パラメータに「time」ではなく「duration」という名前を付けます。

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

例外:

期間がタイムアウト値に具体的に適用される場合は、「timeout」が適切です。

期間ではなく特定の時点を参照する場合は、タイプが java.time.Instant の「time」が適切です。

期間または時間をプリミティブとして表すメソッドは、時間単位で命名し、long を使用する必要があります

プリミティブとして期間を受け取るか返すメソッドは、メソッド名の末尾に関連する時間単位(MillisNanosSeconds など)を付加して、java.time.Duration で使用する装飾なしの名前を予約する必要があります。時間をご覧ください。

メソッドには、単位と時間ベースを適切にアノテーションする必要があります。

  • @CurrentTimeMillisLong: 値は、1970-01-01T00:00:00Z からのミリ秒数として測定された非負のタイムスタンプです。
  • @CurrentTimeSecondsLong: 値は、1970-01-01T00:00:00Z からの秒数として測定された非負のタイムスタンプです。
  • @DurationMillisLong: 値はミリ秒単位の負でない期間です。
  • @ElapsedRealtimeLong: 値は SystemClock.elapsedRealtime() タイムベースの非負のタイムスタンプです。
  • @UptimeMillisLong: 値は SystemClock.uptimeMillis() タイムベースの非負のタイムスタンプです。

プリミティブな時間パラメータまたは戻り値は、int ではなく long を使用する必要があります。

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

時間の単位を表すメソッドでは、単位名の省略形ではなく、省略しない短縮形を使用することが推奨されます

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

長い時間引数にアノテーションを付ける

このプラットフォームには、long 型の時間単位に強い型指定を行うためのアノテーションがいくつか含まれています。

  • @CurrentTimeMillisLong: 値は 1970-01-01T00:00:00Z からのミリ秒数として測定された非負のタイムスタンプです。したがって、System.currentTimeMillis() タイムベースになります。
  • @CurrentTimeSecondsLong: 値は、1970-01-01T00:00:00Z からの秒数として測定された非負のタイムスタンプです。
  • @DurationMillisLong: 値はミリ秒単位の負でない期間です。
  • @ElapsedRealtimeLong: 値は SystemClock#elapsedRealtime() タイムベースの非負のタイムスタンプです。
  • @UptimeMillisLong: 値は SystemClock#uptimeMillis() タイムベースの非負のタイムスタンプです。

測定単位

時間の単位以外の単位を表すメソッドでは、CamelCase の SI 単位の接頭辞を優先します。

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

オーバーロードの最後にオプションのパラメータを配置する

省略可能なパラメータを持つメソッドのオーバーロードがある場合は、それらのパラメータを末尾に置き、他のパラメータとの順序を維持します。

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

省略可能な引数のオーバーロードを追加する場合、より複雑なメソッドにデフォルトの引数が指定された場合とまったく同じように、よりシンプルなメソッドが動作する必要があります。

補足: メソッドがポリモーフィックである場合、オプションの引数を追加したり、異なる型の引数を受け入れたりする場合を除き、メソッドをオーバーロードしないでください。オーバーロードされたメソッドが根本的に異なる処理を行う場合は、新しい名前を付けます。

デフォルト パラメータを持つメソッドには @JvmOverloads アノテーションを付ける必要があります(Kotlin のみ)

バイナリ互換性を維持するため、デフォルト パラメータを持つメソッドとコンストラクタには @JvmOverloads アノテーションを付ける必要があります。

詳細については、公式の Kotlin-Java 相互運用ガイドの Function overloads for defaults をご覧ください。

class Greeting @JvmOverloads constructor(
  loudness: Int = 5
) {
  @JvmOverloads
  fun sayHello(prefix: String = "Dr.", name: String) = // ...
}

デフォルトのパラメータ値を削除しない(Kotlin のみ)

メソッドがデフォルト値を持つパラメータとともにリリースされている場合、デフォルト値の削除はソース互換性を損なう変更です。

最も特徴的で識別可能なメソッド パラメータを最初に指定する

複数のパラメータを持つメソッドがある場合は、最も関連性の高いものを最初に配置します。フラグやその他のオプションを指定するパラメータは、操作対象のオブジェクトを記述するパラメータよりも重要度が低くなります。完了コールバックがある場合は、最後に配置します。

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

関連情報: オーバーロードでオプション パラメータを最後に配置する

ビルダー

Builder パターンは、複雑な Java オブジェクトの作成に推奨され、Android では次のような場合に一般的に使用されます。

  • 結果のオブジェクトのプロパティは変更不可である必要があります
  • 必要なプロパティが多数あります(コンストラクタ引数など)。
  • 構築時のプロパティ間には複雑な関係があります(検証ステップが必要など)。このレベルの複雑さは、API のユーザビリティに関する問題を示していることがよくあります。

ビルダーが必要かどうかを検討します。ビルダーは、次のような目的で使用される場合に API サーフェスで役立ちます。

  • オプションの作成パラメータの大きなセットのうち、ほんの数個だけを構成する
  • 多くの異なるオプション パラメータまたは必須パラメータ(類似または一致する型の場合もある)を構成します。これにより、呼び出しサイトの読み取りが混乱したり、書き込みでエラーが発生したりするのを防ぐことができます。
  • オブジェクトの作成を段階的に構成します。複数の異なる構成コードが、実装の詳細としてビルダーを呼び出す可能性があります。
  • 将来の API バージョンで追加のオプションの作成パラメータを追加して、型を拡張できるようにする

必須パラメータが 3 つ以下で、省略可能なパラメータがない型がある場合は、ほとんどの場合、ビルダーをスキップしてプレーン コンストラクタを使用できます。

Kotlin ソースのクラスでは、ビルダーよりもデフォルトの引数を持つ @JvmOverloads アノテーション付きのコンストラクタが優先されますが、前述のケースでビルダーも提供することで、Java クライアントのユーザビリティを向上させることもできます。

class Tone @JvmOverloads constructor(
  val duration: Long = 1000,
  val frequency: Int = 2600,
  val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
  class Builder {
    // ...
  }
}

ビルダークラスはビルダーを返す必要があります

ビルダークラスは、build() 以外のすべてのメソッドから Builder オブジェクト(this など)を返すことで、メソッド チェーンを有効にする必要があります。追加のビルド オブジェクトは引数として渡す必要があります。別のオブジェクトのビルダーを返さないでください。次に例を示します。

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

基本ビルダー クラスが拡張機能をサポートする必要があるまれなケースでは、汎用戻り値の型を使用します。

public abstract class Builder<T extends Builder<T>> {
  abstract T setValue(int);
}

public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
  T setValue(int);
  T setTypeSpecificValue(long);
}

ビルダー クラスはコンストラクタを介して作成する必要があります

Android API サーフェス全体で一貫したビルダー作成を維持するため、すべてのビルダーは静的作成メソッドではなくコンストラクタを通じて作成されなければなりません。Kotlin ベースの API の場合、Kotlin ユーザーがファクトリ メソッド/DSL スタイルの作成メカニズムを通じてビルダーに暗黙的に依存することが想定されている場合でも、Builder は public である必要があります。ライブラリは、@PublishedApi internal を使用して Builder クラス コンストラクタを Kotlin クライアントから選択的に非表示にしてはなりません

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

ビルダー コンストラクタの引数はすべて必須(@NonNull など)である必要があります

省略可能な引数(例: @Nullable)は、セッター メソッドに移動する必要があります。必要な引数が指定されていない場合、ビルダーのコンストラクタは NullPointerException をスローする必要があります(Objects.requireNonNull の使用を検討してください)。

Builder クラスは、構築される型の final static 内部クラスであるべきです

パッケージ内の論理的な編成のために、通常、ビルダー クラスは、構築された型の最終的な内部クラスとして公開されるべきです(たとえば、ToneBuilder ではなく Tone.Builder)。

ビルダーには、既存のインスタンスから新しいインスタンスを作成するコンストラクタを含めることができます。

ビルダーには、既存のビルダーまたは構築されたオブジェクトから新しいビルダー インスタンスを作成するためのコピー コンストラクタを含めることができます。既存のビルダーまたはビルド オブジェクトからビルダー インスタンスを作成する代替メソッドを提供すべきではありません

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
ビルダーにコピー コンストラクタがある場合、ビルダー セッターは @Nullable 引数を取るべきです

既存のインスタンスからビルダーの新しいインスタンスを作成する可能性がある場合は、リセットが不可欠です。コピー コンストラクタがない場合、ビルダーには @Nullable または @NonNullable 引数のいずれかが含まれている可能性があります。

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}
ビルダー セッターは、省略可能なプロパティに @Nullable 引数を取ることができる

特に Kotlin では、ビルダーやオーバーロードの代わりにデフォルトの引数を使用するため、2 次入力に null 許容値を使用する方が簡単な場合が多くあります。

また、@Nullable セッターはゲッターと一致します。オプションのプロパティの場合、ゲッターは @Nullable である必要があります。

Value createValue(@Nullable OptionalValue optionalValue) {
  Value.Builder builder = new Value.Builder();
  if (optionalValue != null) {
    builder.setOptionalValue(optionalValue);
  }
  return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
  return new Value.Builder()
    .setOptionalValue(optionalValue);
    .build();
}

// Or in other cases:

Value createValue() {
  return new Value.Builder()
    .setOptionalValue(condition ? new OptionalValue() : null);
    .build();
}

Kotlin での一般的な使用方法:

fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .apply { optionalValue?.let { setOptionalValue(it) } }
    .build()
fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .setOptionalValue(optionalValue)
    .build()

デフォルト値(セッターが呼び出されない場合)と null の意味は、セッターとゲッターの両方で適切に文書化する必要があります。

/**
 * ...
 *
 * <p>Defaults to {@code null}, which means the optional value won't be used.
 */

ビルダー セッターは、構築されたクラスでセッターが使用可能な可変プロパティに指定できます

クラスに可変プロパティがあり、Builder クラスが必要な場合は、まず、クラスに可変プロパティが実際に必要かどうかを検討してください。

次に、可変プロパティが必要であることが確実な場合は、次のシナリオのうち、想定されるユースケースに適しているものを判断します。

  1. 構築されたオブジェクトはすぐに使用できる必要があります。そのため、変更可能か変更不可能かを問わず、関連するすべてのプロパティにセッターを提供するべきです

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. 構築されたオブジェクトが有用になる前に、追加の呼び出しが必要になる場合があるため、可変プロパティにはセッターを設けるべきではありません。

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

2 つのシナリオを混同しないでください。

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

ビルダーにゲッターは不要

ゲッターはビルダーではなく、構築されたオブジェクトに配置する必要があります。

ビルダーのセッターには、ビルドされたクラスに対応するゲッターが必要です

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

ビルダー メソッドの命名

ビルダー メソッドの名前は、setFoo()addFoo()、または clearFoo() のスタイルを使用する必要があります。

ビルダー クラスは build() メソッドを宣言することが想定されています

ビルダー クラスは、構築されたオブジェクトのインスタンスを返す build() メソッドを宣言する必要があります。

Builder の build() メソッドは @NonNull オブジェクトを返す必要があります

ビルダーの build() メソッドは、構築されたオブジェクトの null 以外のインスタンスを返すことが想定されています。パラメータが無効なためにオブジェクトを作成できない場合は、検証をビルドメソッドに延期し、IllegalStateException をスローする必要があります。

内部ロックを公開しない

公開 API のメソッドは synchronized キーワードを使用すべきではありません。このキーワードを使用すると、オブジェクトまたはクラスがロックとして使用されます。また、他のコードにも公開されるため、クラス外の他のコードがロック目的で使用し始めると、予期しない副作用が発生する可能性があります。

代わりに、必要なロックを内部のプライベート オブジェクトに対して実行します。

public synchronized void doThing() { ... }
private final Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

アクセサ スタイルのメソッドは Kotlin プロパティのガイドラインに準拠すべき

Kotlin ソースから見ると、getsetis 接頭辞を使用するアクセサ スタイルのメソッドも Kotlin プロパティとして使用できます。たとえば、Java で定義された int getField() は、Kotlin ではプロパティ val field: Int として使用できます。

このため、また、一般的にアクセサー メソッドの動作に関するデベロッパーの期待に応えるため、アクセサー メソッドの接頭辞を使用するメソッドは、Java フィールドと同様に動作する必要があります。次の場合、アクセサー スタイルの接頭辞は使用しないでください。

  • メソッドに副作用がある -- より説明的なメソッド名が望ましい
  • このメソッドには計算コストの高い作業が含まれます。compute を優先してください
  • このメソッドでは、IPC やその他の I/O など、値を返すためにブロックまたは長時間実行される作業を行います。fetch を優先します。
  • 値を返せるまでスレッドをブロックするメソッド - await を優先
  • このメソッドは呼び出しごとに新しいオブジェクト インスタンスを返します。create を使用することをおすすめします
  • メソッドが値を正常に返さない可能性があるため、request を優先してください

計算コストの高い作業を 1 回実行して、後続の呼び出しのために値をキャッシュに保存する場合も、計算コストの高い作業を実行したとみなされます。ジャンクはフレーム間で償却されません。

ブール値アクセサ メソッドに is 接頭辞を使用する

これは、Java のブール値メソッドとフィールドの標準的な命名規則です。一般に、ブール値のメソッドと変数の名前は、戻り値で回答される質問として記述する必要があります。

Java のブール値アクセサ メソッドは set/is の命名スキームに従うべきであり、フィールドは is を優先すべきです。例:

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Java アクセサ メソッドに set/is を使用するか、Java フィールドに is を使用すると、Kotlin からプロパティとして使用できるようになります。

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

プロパティとアクセサ メソッドは、一般的に Disabled ではなく Enabled のように肯定的な命名を使用する必要があります。否定的な用語を使用すると、truefalse の意味が反転し、動作を推論することが難しくなります。

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

ブール値がプロパティの包含または所有権を表す場合は、is ではなく has を使用できます。ただし、これは Kotlin プロパティ構文では機能しません。

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

より適切な代替プレフィックスとしては、canshould などがあります。

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

動作や機能を切り替えるメソッドでは、接頭辞 is と接尾辞 Enabled を使用できます。

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

同様に、他の動作や機能への依存性を示すメソッドは、is プレフィックスと Supported または Required サフィックスを使用できます。

// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()

一般に、メソッド名は戻り値で回答される質問として記述する必要があります。

Kotlin のプロパティ メソッド

クラス プロパティ var foo: Foo の場合、Kotlin は一貫したルールを使用して get/set メソッドを生成します。ゲッターの場合は get を先頭に追加して最初の文字を大文字にし、セッターの場合は set を先頭に追加して最初の文字を大文字にします。プロパティ宣言により、それぞれ public Foo getFoo()public void setFoo(Foo foo) という名前のメソッドが生成されます。

プロパティの型が Boolean の場合、名前の生成に別のルールが適用されます。プロパティ名が is で始まる場合、ゲッター メソッド名に get は付加されず、プロパティ名自体がゲッターとして使用されます。そのため、命名ガイドラインに沿って、is 接頭辞を使用して Boolean プロパティに名前を付けることをおすすめします

var isVisible: Boolean

プロパティが上記の例外のいずれかで、適切な接頭辞で始まる場合は、プロパティの @get:JvmName アノテーションを使用して、適切な名前を手動で指定します。

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

ビットマスク アクセサ

ビットマスク フラグの定義に関する API ガイドラインについては、ビットマスク フラグに @IntDef を使用するをご覧ください。

セッター

2 つのセッター メソッドを提供する必要があります。1 つは完全なビット文字列を受け取り、既存のすべてのフラグを上書きするメソッド、もう 1 つはカスタム ビットマスクを受け取り、より柔軟な対応を可能にするメソッドです。

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the specified types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

ゲッター

完全なビットマスクを取得するために、1 つのゲッターを提供する必要があります。

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

protected の代わりに public を使用する

公開 API では常に protected よりも public を優先します。保護されたアクセスは、デフォルトで外部アクセスが同程度に有効な場合に、実装者がパブリック アクセサーを提供するためにオーバーライドする必要があるため、長期的には面倒なものになります。

protected の可視性は、デベロッパーが API を呼び出すことを防ぐものではありません。単に少し煩わしくなるだけです。

equals() と hashCode() のいずれも実装しないか、両方実装する

一方をオーバーライドする場合は、もう一方もオーバーライドする必要があります。

データクラスの toString() を実装する

デベロッパーがコードをデバッグできるように、データクラスで toString() をオーバーライドすることが推奨されます。

出力がプログラムの動作用かデバッグ用かを文書化する

プログラムの動作を実装に依存させるかどうかを決定します。たとえば、UUID.toString()File.toString() は、プログラムで使用する特定の形式をドキュメント化します。Intent など、デバッグ専用の情報を公開する場合は、スーパークラスからドキュメントを継承することを意味します。

余分な情報は含めないでください

toString() から利用できるすべての情報は、オブジェクトの公開 API からも利用できる必要があります。そうしないと、デベロッパーが toString() 出力を解析して依存するようになり、今後の変更が妨げられます。オブジェクトの公開 API のみを使用して toString() を実装することをおすすめします。

デバッグ出力への依存を避ける

デベロッパーがデバッグ出力に依存することを防ぐことはできませんが、オブジェクトの System.identityHashCodetoString() 出力に含めることで、2 つの異なるオブジェクトが同じ toString() 出力を持つ可能性は非常に低くなります。

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

これにより、デベロッパーがオブジェクトに assertThat(a.toString()).isEqualTo(b.toString()) のようなテスト アサーションを記述することを効果的に防ぐことができます。

新しく作成されたオブジェクトを返す場合は createFoo を使用する

新しいオブジェクトを構築するなどして戻り値を作成するメソッドには、接頭辞 getnew ではなく、接頭辞 create を使用します。

メソッドが戻り値としてオブジェクトを作成する場合は、メソッド名でそのことを明確にします。

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

File オブジェクトを受け取るメソッドはストリームも受け取るべき

Android のデータ保存場所は、必ずしもディスク上のファイルとは限りません。たとえば、ユーザー境界を越えて渡されたコンテンツは content:// Uri として表されます。さまざまなデータソースの処理を有効にするには、File オブジェクトを受け入れる API も InputStreamOutputStream、またはその両方を受け入れる必要があります。

public void setDataSource(File file)
public void setDataSource(InputStream stream)

ボックス化されたバージョンではなく、生のプリミティブを取得して返す

欠損値または null 値を伝達する必要がある場合は、-1Integer.MAX_VALUEInteger.MIN_VALUE の使用を検討してください。

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

プリミティブ型のクラス相当を避けると、これらのクラスのメモリ オーバーヘッド、値へのメソッド アクセス、さらに重要なプリミティブ型とオブジェクト型の間のキャストから生じる自動ボックス化を回避できます。こうした動作を避けることで、メモリと一時割り当てを節約し、コストのかかるガベージ コレクションの頻度を減らすことができます。

アノテーションを使用して有効なパラメータ値と戻り値を明確にする

さまざまな状況で許容される値を明確にするために、デベロッパー アノテーションが追加されました。これにより、ツールが開発者の誤った値の提供(フレームワークが特定の定数値のセットの 1 つを必要とする場合に任意の int を渡すなど)を支援しやすくなります。必要に応じて、次のアノテーションをすべて使用します。

null 可能性

Java API では明示的な null 可能性アノテーションが必要ですが、null 可能性の概念は Kotlin 言語の一部であり、Kotlin API で null 可能性アノテーションを使用することはできません。

@Nullable: 指定された戻り値、パラメータ、フィールドが null になる可能性があることを示します。

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull: 指定された戻り値、パラメータ、フィールドが null 値を許容しないことを示します。Android での @Nullable のマーク付けは比較的新しい機能であるため、Android の API メソッドのほとんどは一貫してドキュメント化されていません。そのため、「不明、@Nullable@NonNull」の 3 つの状態があります。これが、@NonNull が API ガイドラインに含まれている理由です。

@NonNull
public String getName()

public void setName(@NonNull String name)

Android プラットフォームのドキュメントでは、メソッド パラメータにアノテーションを付けると、「この値は null になる可能性があります」という形式のドキュメントが自動的に生成されます。ただし、パラメータのドキュメントで「null」が明示的に使用されている場合は除きます。

既存の「実際には null 許容ではない」メソッド: 宣言された @Nullable アノテーションのない API の既存のメソッドは、メソッドが特定の明らかな状況(findViewById() など)で null を返すことができる場合、@Nullable アノテーションを付与できます。null チェックを望まないデベロッパーのために、IllegalArgumentException をスローするコンパニオン @NotNull requireFoo() メソッドを追加する必要があります。

インターフェース メソッド: 新しい API は、インターフェース メソッドを実装する際に適切なアノテーション(Parcelable.writeToParcel() など)を追加する必要があります(つまり、実装クラスのメソッドは writeToParcel(Parcel, int) ではなく writeToParcel(@NonNull Parcel, int) にする必要があります)。ただし、アノテーションがない既存の API を「修正」する必要はありません。

null 可能性の適用

Java では、Objects.requireNonNull() を使用して @NonNull パラメータの入力検証を行い、パラメータが null の場合は NullPointerException をスローすることが推奨されています。これは Kotlin で自動的に実行されます。

リソース

リソース識別子: 特定のリソースの ID を示す整数パラメータには、適切なリソースタイプ定義でアノテーションを付ける必要があります。包括的な @AnyRes に加えて、@StringRes@ColorRes@AnimRes など、リソースのタイプごとにアノテーションがあります。次に例を示します。

public void setTitle(@StringRes int resId)

定数セットの @IntDef

マジック定数: 公開定数で示される有限の可能な値のセットの 1 つを受け取ることを目的とする String パラメータと int パラメータは、@StringDef または @IntDef で適切にアノテーションを付ける必要があります。これらのアノテーションを使用すると、許容されるパラメータの typedef のように機能する新しいアノテーションを作成できます。次に例を示します。

/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

メソッドは、アノテーション付きパラメータの有効性をチェックし、パラメータが @IntDef の一部でない場合は IllegalArgumentException をスローすることが推奨されます。

ビットマスク フラグの @IntDef

アノテーションでは、定数がフラグであることを指定することもできます。また、& と I を組み合わせて使用することもできます。

/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

文字列定数セットの @StringDef

@StringDef アノテーションもあります。これは前のセクションの @IntDef とまったく同じですが、String 定数用です。複数の「prefix」値を含めることができます。これらの値は、すべての値のドキュメントを自動的に出力するために使用されます。

SDK 定数の @SdkConstant

@SdkConstant SdkConstant 値(ACTIVITY_INTENT_ACTIONBROADCAST_INTENT_ACTIONSERVICE_ACTIONINTENT_CATEGORYFEATURE のいずれか)である場合は、公開フィールドにアノテーションを付けます。

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

オーバーライドに互換性のある null 可能性を提供する

API の互換性を維持するため、オーバーライドの null 許容性は、親の現在の null 許容性と互換性がある必要があります。次の表に、互換性の想定値を示します。つまり、オーバーライドは、オーバーライドする要素と同じか、それよりも制限が厳しくなければなりません。

タイプ 子供あり 子供用
戻り値の型 アノテーションなし アノテーションなしまたは非 null
戻り値の型 null 許容 Nullable または nonnull
戻り値の型 Nonnull Nonnull
楽しい引数 アノテーションなし アノテーションなしまたは null 許容
楽しい引数 null 許容 null 許容
楽しい引数 Nonnull Nullable または nonnull

可能な場合は、null 不可(@NonNull など)の引数を使用する

メソッドがオーバーロードされている場合は、すべての引数が非 null であることが望ましい。

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

このルールは、オーバーロードされたプロパティ セッターにも適用されます。プライマリ引数は null 以外にする必要があります。プロパティのクリアは別のメソッドとして実装する必要があります。これにより、デベロッパーが必須ではない末尾のパラメータを設定しなければならない「無意味な」呼び出しを防ぐことができます。

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

コンテナには null 許容でない(@NonNull など)戻り値の型を使用する

BundleCollection などのコンテナ型の場合は、空の(該当する場合は不変の)コンテナを返します。null を使用してコンテナの可用性を区別する場合は、別のブール値メソッドの提供を検討してください。

@NonNull
public Bundle getExtras() { ... }

get と set のペアの Null 可能性アノテーションは一致している必要があります

単一の論理プロパティの get メソッドと set メソッドのペアは、常に null 可能性のアノテーションで一致している必要があります。このガイドラインに従わないと、Kotlin のプロパティ構文が機能しなくなります。したがって、既存のプロパティ メソッドに矛盾する null 可能性アノテーションを追加すると、Kotlin ユーザーにとってソース破壊的な変更になります。

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

失敗またはエラー条件での戻り値

すべての API で、アプリがエラーに対応できるようにする必要があります。false-1null、または「問題が発生しました」などの汎用的な値を返しても、ユーザーの期待値を設定したり、フィールドでアプリの信頼性を正確に追跡したりするのに十分な情報をデベロッパーに伝えることはできません。API を設計する際は、アプリを構築していると想定してください。エラーが発生した場合、API はユーザーに提示したり、適切に対応したりするのに十分な情報を提供しますか?

  1. 例外メッセージに詳細情報を含めることは問題ありません(むしろ推奨されます)が、エラーを適切に処理するためにデベロッパーが例外メッセージを解析する必要はありません。詳細なエラーコードやその他の情報はメソッドとして公開する必要があります。
  2. 選択したエラー処理オプションで、将来的に新しいエラータイプを導入できる柔軟性が確保されていることを確認してください。@IntDef の場合、OTHER または UNKNOWN の値を含めることを意味します。新しいコードを返すときに、呼び出し元の targetSdkVersion を確認して、アプリが認識していないエラーコードを返さないようにすることができます。例外については、例外が実装する共通のスーパー クラスを用意して、その型を処理するコードがサブタイプもキャッチして処理するようにします。
  3. 開発者が誤ってエラーを無視することが難しいか、不可能であるべきです。値を返すことでエラーが伝達される場合は、メソッドに @CheckResult のアノテーションを付けます。

デベロッパーのミス(入力パラメータの制約の無視、オブザーバブル状態のチェックの失敗など)が原因で失敗またはエラー状態になった場合は、? extends RuntimeException をスローすることが推奨されます。

セッター メソッドまたはアクション メソッド(perform など)は、非同期で更新された状態やデベロッパーの制御外の条件の結果としてアクションが失敗する可能性がある場合、整数ステータス コードを返すことがあります。

ステータス コードは、包含クラスで public static final フィールドとして定義し、ERROR_ をプレフィックスとして付け、@hide @IntDef アノテーションで列挙する必要があります。

メソッド名は常に動詞で始まり、主語で始まってはならない

メソッドの名前は、常に動詞(getcreatereload など)で始まり、操作対象のオブジェクトで始まってはなりません。

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

戻り値またはパラメータの型として配列よりも Collection 型を優先する

ジェネリック型コレクション インターフェースには、一意性と順序付けに関するより強力な API コントラクト、ジェネリクスのサポート、デベロッパーに使いやすい便利なメソッドなど、配列よりも優れた点がいくつかあります。

プリミティブの例外

要素がプリミティブの場合、自動ボックス化のコストを回避するために、配列を優先しますボックス化されたバージョンではなく、生のプリミティブを取得して返すを参照してください。

パフォーマンス重視のコードの例外

パフォーマンスが重要なコード(グラフィックやその他の測定/レイアウト/描画 API など)で API が使用される特定のシナリオでは、割り当てとメモリのチャーンを減らすために、コレクションの代わりに配列を使用してもかまいません。

Kotlin の例外

Kotlin 配列は不変であり、Kotlin 言語には配列に関するユーティリティ API が豊富に用意されているため、Kotlin からアクセスすることを目的とした Kotlin API の ListCollection と同等です。

@NonNull コレクションを優先する

コレクション オブジェクトには常に @NonNull を使用します。空のコレクションを返す場合は、適切な Collections.empty メソッドを使用して、低コストで型が正しく、不変のコレクション オブジェクトを返します。

型アノテーションがサポートされている場合は、コレクション要素に常に @NonNull を使用します。

コレクションではなく配列を使用する場合は、@NonNull を優先する必要があります(前の項目を参照)。オブジェクトの割り当てが気になる場合は、定数を作成して渡します。空の配列は不変です。例:

private static final int[] EMPTY_USER_IDS = new int[0];

@NonNull
public int[] getUserIds() {
  int [] userIds = mService.getUserIds();
  return userIds != null ? userIds : EMPTY_USER_IDS;
}

コレクションの可変性

Kotlin API は、API コントラクトで可変の戻り値の型が明示的に必要な場合を除き、デフォルトでコレクションに読み取り専用(Mutable ではない)の戻り値の型を使用する必要があります。

ただし、Java API の Android プラットフォーム実装では、不変コレクションの便利な実装がまだ提供されていないため、Java API ではデフォルトで可変の戻り値の型が優先されます。このルールの例外は、変更不可能な Collections.empty 戻り値の型です。クライアントが意図的または誤って可変性を利用して API の意図した使用パターンを壊す可能性がある場合は、Java API でコレクションのシャロー コピーを返すことを強く検討する必要があります。

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

明示的に変更可能な戻り値の型

コレクションを返す API は、返したコレクション オブジェクトを返した後に変更しないことが理想的です。返されたコレクションが変更されるか、何らかの方法で再利用される必要がある場合(たとえば、変更可能なデータセットの適応ビューなど)、コンテンツを変更できるタイミングの正確な動作を明示的に文書化するか、確立された API 命名規則に従う必要があります。

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

Kotlin の .asFoo() 規約については、下記をご覧ください。この規約では、元のコレクションが変更された場合に .asList() から返されるコレクションを変更できます。

返されるデータ型オブジェクトの可変性

コレクションを返す API と同様に、データ型オブジェクトを返す API は、返した後に返されたオブジェクトのプロパティを変更しないことが理想的です。

val tempResult = DataContainer()

fun add(other: DataContainer): DataContainer {
  tempResult.innerValue = innerValue + other.innerValue
  return tempResult
}
fun add(other: DataContainer): DataContainer {
  return DataContainer(innerValue + other.innerValue)
}

非常に限られたケースでは、パフォーマンスが重要なコードでオブジェクト プールや再利用が有効な場合があります。独自のオブジェクト プール データ構造を作成したり、再利用されたオブジェクトを公開 API で公開したりしないでください。いずれの場合も、同時アクセスを管理する際は十分に注意してください。

可変長引数パラメータ型の使用

Kotlin API と Java API の両方で、同じ型の関連する複数のパラメータを渡すためだけに呼び出しサイトで配列を作成する可能性が高い場合は、vararg を使用することが推奨されます。

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

防御のためのコピー

vararg パラメータの Java 実装と Kotlin 実装はどちらも同じ配列バッキング バイトコードにコンパイルされるため、Java コードから可変配列で呼び出すことができます。配列パラメータがフィールドまたは匿名内部クラスに永続化される場合は、API 設計者は防御的なシャロー コピーを作成することが強く推奨されます。

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

防御コピーを作成しても、最初のメソッド呼び出しとコピーの作成の間の同時変更に対する保護は提供されません。また、配列に含まれるオブジェクトのミューテーションに対する保護も提供されません。

コレクション型のパラメータまたは戻り値の型で正しいセマンティクスを提供する

List<Foo> はデフォルトのオプションですが、他のタイプを検討して意味を追加することもできます。

  • API が要素の順序を区別せず、重複を許可しない場合や重複が意味をなさない場合は、Set<Foo> を使用します。

  • API が順序を気にせず、重複を許可する場合は Collection<Foo>,

Kotlin 変換関数

Kotlin では、.toFoo().asFoo() を使用して、既存のオブジェクトから別の型のオブジェクトを取得することがよくあります。ここで、Foo は変換の戻り値の型の名前です。これは、おなじみの JDK Object.toString() と一致しています。Kotlin では、25.toFloat() などのプリミティブ変換にも使用されます。

.toFoo().asFoo() という名前のコンバージョンの違いは重要です。

新しい独立したオブジェクトを作成する場合は .toFoo() を使用する

.toString() と同様に、「to」変換は新しい独立したオブジェクトを返します。元のオブジェクトが後で変更されても、新しいオブジェクトにはその変更が反映されません。同様に、新しいオブジェクトが後で変更されても、古いオブジェクトにはその変更が反映されません。

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

依存ラッパー、装飾オブジェクト、キャストを作成するときは .asFoo() を使用する

Kotlin でのキャストは、as キーワードを使用して行います。これは、インターフェースの変更を反映したものであり、ID の変更を反映したものではありません。拡張関数で接頭辞として使用される場合、.asFoo() はレシーバーを修飾します。元のレシーバー オブジェクトの変更は、asFoo() が返すオブジェクトに反映されます。新しい Foo オブジェクトのミューテーションは、元のオブジェクトに反映されることがあります。

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

変換関数は拡張関数として記述する必要があります

レシーバと結果クラスの両方の定義の外で変換関数を記述すると、型間の結合が軽減されます。理想的な変換では、元のオブジェクトへのパブリック API アクセスのみが必要です。これは、デベロッパーが同様の変換を独自の優先型に書き込むこともできることを例で示しています。

適切な特定の例外をスローする

メソッドは java.lang.Exceptionjava.lang.Throwable などの一般的な例外をスローしてはなりません。代わりに、デベロッパーが例外を広範囲に処理することなく処理できるように、java.lang.NullPointerException などの適切な特定の例外を使用する必要があります。

公開呼び出しメソッドに直接渡された引数に関係のないエラーは、java.lang.IllegalArgumentException または java.lang.NullPointerException ではなく java.lang.IllegalStateException をスローする必要があります。

リスナーとコールバック

リスナーとコールバック メカニズムに使用されるクラスとメソッドに関するルールは次のとおりです。

コールバック クラス名は単数形にする

MyObjectCallbacks ではなく MyObjectCallback を使用します。

コールバック メソッド名は on の形式にする必要があります

onFooEvent は、FooEvent が発生しており、コールバックが応答する必要があることを示します。

過去形と現在形はタイミングの動作を説明する

イベントに関するコールバック メソッドは、イベントがすでに発生したか、発生中かを示す名前を付ける必要があります。

たとえば、クリック アクションの実行後にメソッドが呼び出された場合:

public void onClicked()

ただし、メソッドがクリック アクションの実行を担当している場合は、

public boolean onClick()

コールバックの登録

リスナーまたはコールバックをオブジェクトに追加または削除できる場合、関連するメソッドには add と remove または register と unregister という名前を付ける必要があります。クラスまたは同じパッケージ内の他のクラスで使用されている既存の規約と一貫性を持たせます。そのような前例がない場合は、追加と削除を優先します。

コールバックの登録または登録解除を行うメソッドは、コールバック型の完全な名前を指定する必要があります。

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

コールバックのゲッターを避ける

getFooCallback() メソッドを追加しないでください。これは、デベロッパーが既存のコールバックを独自の置換と連結したい場合に魅力的なエスケープ ハッチですが、脆弱であり、コンポーネント デベロッパーが現在の状態を推論することを困難にします。たとえば次のようにリクエストします。

  • デベロッパー A が setFooCallback(a) を呼び出す
  • デベロッパー B が setFooCallback(new B(getFooCallback())) を呼び出す
  • デベロッパー A はコールバック a を削除したいのですが、B の型を知らず、また B がラップされたコールバックのそのような変更を許可するように構築されていないため、削除する方法がありません。

コールバックのディスパッチを制御する Executor を受け入れる

明示的なスレッド処理の期待値がないコールバック(UI ツールキット以外のほぼすべての場所)を登録する場合は、登録の一部として Executor パラメータを含めることを強く推奨します。これにより、デベロッパーはコールバックが呼び出されるスレッドを指定できます。

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

通常のオプション パラメータに関するガイドラインの例外として、パラメータ リストの最後の引数ではない場合でも、Executor を省略したオーバーロードを提供することは許容されます。Executor が指定されていない場合、コールバックは Looper.getMainLooper() を使用してメインスレッドで呼び出す必要があります。これは、関連するオーバーロード メソッドで文書化する必要があります。

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Executor 実装の注意点: 次の実行プログラムは有効です。

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

つまり、この形式の API を実装する場合、アプリ プロセス側の受信バインダ オブジェクトの実装は、アプリが提供する Executor でアプリのコールバックを呼び出す前に Binder.clearCallingIdentity() を呼び出す必要があります。これにより、権限チェックにバインダ ID(Binder.getCallingUid() など)を使用するアプリコードは、アプリに実行中のコードを正しく関連付け、アプリを呼び出すシステム プロセスには関連付けません。API のユーザーが呼び出し元の UID または PID 情報を必要とする場合は、提供された Executor の実行場所に基づく暗黙的なものではなく、API サーフェスの明示的な一部にする必要があります。

Executor の指定は、API でサポートされるべきです。パフォーマンスが重要なケースでは、アプリがコードを直ちに実行するか、API からのフィードバックと同期して実行する必要がある場合があります。Executor を受け入れると、これが許可されます。防御的に追加の HandlerThread またはトランポリンに類似したものを生成すると、この望ましいユースケースが実現できなくなります。

アプリが独自のプロセス内のどこかで高コストのコードを実行する場合は、許可します。アプリ デベロッパーが制限を回避するために見つけた回避策は、長期的にサポートすることが非常に困難になります。

単一のコールバックの例外: 報告されるイベントの性質上、単一のコールバック インスタンスのみをサポートする必要がある場合は、次のスタイルを使用します。

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Handler ではなく Executor を使用する

以前は、コールバック実行を特定の Looper スレッドにリダイレクトする標準として Android の Handler が使用されていました。この標準は Executor を優先するように変更されました。ほとんどのアプリ デベロッパーが独自のスレッド プールを管理しており、メインスレッドまたは UI スレッドがアプリで使用できる唯一の Looper スレッドになっているためです。Executor を使用すると、デベロッパーは既存の実行コンテキストや優先する実行コンテキストを再利用するために必要な制御を行うことができます。

kotlinx.coroutines や RxJava などの最新の並行処理ライブラリは、必要に応じて独自のディスパッチを実行する独自のスケジューリング メカニズムを提供します。このため、二重スレッド ホップによるレイテンシを回避するために、直接実行ツール(Runnable::run など)を使用する機能を提供することが重要です。たとえば、Handler を使用して Looper スレッドに投稿する 1 つのホップと、アプリの同時実行フレームワークからの別のホップがあります。

このガイドラインの例外はまれです。例外に対する一般的な再審査請求は次のとおりです。

イベントで epoll するために Looper が必要なので、Looper を使用する必要があります。この例外リクエストは、この状況では Executor のメリットを実現できないため、承認されます。

アプリコードがイベントを公開するスレッドをブロックしないようにしたい。通常、アプリプロセスで実行されるコードに対してこの例外リクエストが許可されることはありません。この値を誤って取得したアプリは、システム全体の健全性に影響を与えるのではなく、アプリ自体に悪影響を及ぼします。適切な処理を行っているアプリや、一般的な並行処理フレームワークを使用しているアプリは、レイテンシのペナルティを課せられることはありません。

Handler は、同じクラスの他の類似 API とローカルで整合しています。この例外リクエストは、状況に応じて承認されます。Executor ベースのオーバーロードを追加し、Handler 実装を移行して新しい Executor 実装を使用することが推奨されます。(myHandler::post は有効な Executor です)。クラスのサイズ、既存の Handler メソッドの数、デベロッパーが新しいメソッドと並行して既存の Handler ベースのメソッドを使用する必要がある可能性に応じて、新しい Handler ベースのメソッドを追加するための例外が認められる場合があります。

登録時の対称性

追加または登録する方法がある場合は、削除または登録解除する方法も必要です。メソッド

registerThing(Thing)

一致する

unregisterThing(Thing)

リクエスト ID を指定する

デベロッパーがコールバックを再利用することが妥当な場合は、コールバックをリクエストに関連付けるための識別子オブジェクトを提供します。

class RequestParameters {
  public int getId() { ... }
}

class RequestExecutor {
  public void executeRequest(
    RequestParameters parameters,
    Consumer<RequestParameters> onRequestCompletedListener) { ... }
}

複数メソッドのコールバック オブジェクト

複数のメソッドのコールバックでは、interface を優先し、以前にリリースされたインターフェースに追加する場合は default メソッドを使用する必要があります。以前は、Java 7 に default メソッドがなかったため、このガイドラインでは abstract class を推奨していました。

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

非ブロッキング関数呼び出しをモデル化する場合は android.os.OutcomeReceiver を使用する

OutcomeReceiver<R,E> は、成功した場合は結果値 R を、それ以外の場合は E : Throwable を報告します。これは、通常のメソッド呼び出しと同じです。結果を返すか例外をスローするブロッキング メソッドをノンブロッキング非同期メソッドに変換する場合は、コールバック タイプとして OutcomeReceiver を使用します。

interface FooType {
  // Before:
  public FooResult requestFoo(FooRequest request);

  // After:
  public void requestFooAsync(FooRequest request, Executor executor,
      OutcomeReceiver<FooResult, Throwable> callback);
}

このように変換された非同期メソッドは常に void を返します。requestFoo が返す結果は、提供された executorrequestFooAsynccallback パラメータの OutcomeReceiver.onResult を呼び出すことで、代わりに requestFooAsync に報告されます。requestFoo がスローする例外は、代わりに OutcomeReceiver.onError メソッドに同じ方法で報告されます。

OutcomeReceiver を使用して非同期メソッドの結果をレポートすると、androidx.core:core-ktxContinuation.asOutcomeReceiver 拡張機能を使用する非同期メソッドの Kotlin suspend fun ラッパーも提供されます。

suspend fun FooType.requestFoo(request: FooRequest): FooResult =
  suspendCancellableCoroutine { continuation ->
    requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
  }

このような拡張機能により、Kotlin クライアントは呼び出し元スレッドをブロックすることなく、通常の関数呼び出しの利便性で非ブロッキングの非同期メソッドを呼び出すことができます。プラットフォーム API のこれらの 1 対 1 の拡張機能は、標準バージョンの互換性チェックと考慮事項と組み合わせると、Jetpack の androidx.core:core-ktx アーティファクトの一部として提供される場合があります。詳細、キャンセルに関する考慮事項、サンプルについては、asOutcomeReceiver のドキュメントをご覧ください。

処理の完了時に結果を返すか例外をスローするメソッドのセマンティクスに一致しない非同期メソッドは、コールバック型として OutcomeReceiver を使用すべきではありません。代わりに、次のセクションに記載されている他のオプションのいずれかを検討してください。

新しい単一抽象メソッド(SAM)型を作成するよりも関数型インターフェースを優先する

API レベル 24 で java.util.function.*リファレンス ドキュメント)型が追加されました。これは、コールバック ラムダとして使用するのに適した Consumer<T> などの汎用 SAM インターフェースを提供します。多くの場合、新しい SAM インターフェースを作成しても、型安全性や意図の伝達の面でほとんどメリットがなく、Android API のサーフェス領域が不必要に拡大します。

新しいインターフェースを作成するのではなく、次の汎用インターフェースの使用を検討してください。

SAM パラメータの配置

メソッドが追加のパラメータでオーバーロードされている場合でも、Kotlin からの慣用的な使用を可能にするために、SAM パラメータは最後に配置する必要があります。

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

ドキュメント

これらは、API の公開ドキュメント(Javadoc)に関するルールです。

すべての公開 API を文書化する必要があります

すべての公開 API には、デベロッパーが API を使用する方法を説明する十分なドキュメントが必要です。デベロッパーがオートコンプリートを使用するか、API リファレンス ドキュメントを参照してメソッドを見つけ、隣接する API サーフェス(同じクラスなど)から最小限のコンテキストを取得しているとします。

メソッド

メソッドのパラメータと戻り値は、それぞれ @param@return のドキュメント アノテーションを使用してドキュメント化する必要があります。Javadoc の本文は、「このメソッドは...」で始まるものとしてフォーマットします。

メソッドがパラメータを受け取らず、特別な考慮事項がなく、メソッド名が示す処理を返す場合は、@return を省略して、次のようなドキュメントを記述できます。

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

ドキュメントでは、関連する定数、メソッド、その他の要素について、他のドキュメントへのリンクを貼る必要があります。プレーン テキストの単語だけでなく、Javadoc タグ(@see{@link foo} など)を使用します。

次のソース例の場合:

public static final int FOO = 0;
public static final int BAR = 1;

プレーン テキストやコードフォントを使用しない:

/**
 * Sets value to one of FOO or <code>BAR</code>.
 *
 * @param value the value being set, one of FOO or BAR
 */
public void setValue(int value) { ... }

代わりに、リンクを使用します。

/**
 * Sets value to one of {@link #FOO} or {@link #BAR}.
 *
 * @param value the value being set
 */
public void setValue(@ValueType int value) { ... }

@ValueType などの IntDef アノテーションをパラメータで使用すると、許容される型を指定するドキュメントが自動的に生成されます。IntDef の詳細については、アノテーションに関するガイダンスをご覧ください。

Javadoc を追加するときに update-api または docs ターゲットを実行する

このルールは、@link タグまたは @see タグを追加する場合に特に重要です。出力が想定どおりになっていることを確認してください。Javadoc の ERROR 出力は、多くの場合、リンクの不良が原因です。update-api または docs Make ターゲットのいずれかがこのチェックを実行しますが、Javadoc のみを変更し、update-api ターゲットを実行する必要がない場合は、docs ターゲットの方が高速です。

Java の値を区別するには {@code foo} を使用します

truefalsenull などの Java の値は、ドキュメントのテキストと区別するために {@code...} で囲みます。

Kotlin ソースでドキュメントを記述する場合は、Markdown の場合と同様に、コードをバッククォートで囲むことができます。

@param と @return の概要は 1 つの文の断片にする必要があります

パラメータと戻り値の概要は小文字で始まり、1 つの文の断片のみを含める必要があります。1 文を超える追加情報がある場合は、メソッドの Javadoc 本文に移動します。

/**
 * @param e The element to be appended to the list. This must not be
 *       null. If the list contains no entries, this element will
 *       be added at the beginning.
 * @return This method returns true on success.
 */

この場合は、次のように修正します。

/**
 * @param e element to be appended to this list, must be non-{@code null}
 * @return {@code true} on success, {@code false} otherwise
 */

ドキュメントのメモに説明が必要

アノテーション @hide@removed が公開 API から非表示になっている理由を文書化します。@deprecated アノテーションでマークされた API 要素を置き換える方法の手順を含めます。

@throws を使用して例外を文書化する

メソッドがチェック済みの例外(IOException など)をスローする場合は、@throws で例外を文書化します。Java クライアントで使用することを目的とした Kotlin ソースの API の場合は、関数に @Throws アノテーションを付けます。

メソッドが、防止可能なエラーを示す未チェックの例外(IllegalArgumentExceptionIllegalStateException など)をスローする場合は、例外がスローされる理由の説明とともに例外を文書化します。スローされた例外は、スローされた理由も示す必要があります。

未チェックの例外の中には、暗黙的とみなされ、ドキュメント化する必要がないものもあります。たとえば、引数が @IntDef や、API 契約をメソッド シグネチャに埋め込む同様のアノテーションと一致しない NullPointerExceptionIllegalArgumentException などです。

/**
 * ...
 * @throws IOException If it cannot find the schema for {@code toVersion}
 * @throws IllegalStateException If the schema validation fails
 */
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
    boolean validateDroppedTables, Migration... migrations) throws IOException {
  // ...
  if (!dbPath.exists()) {
    throw new IllegalStateException("Cannot find the database file for " + name
        + ". Before calling runMigrations, you must first create the database "
        + "using createDatabase.");
  }
  // ...

または、Kotlin では次のようになります。

/**
 * ...
 * @throws IOException If something goes wrong reading the file, such as a bad
 *                     database header or missing permissions
 */
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
  // ...
  val read = input.read(buffer)
    if (read != 4) {
      throw IOException("Bad database header, unable to read 4 bytes at " +
          "offset 60")
    }
  }
  // ...

メソッドが例外をスローする可能性のある非同期コードを呼び出す場合は、デベロッパーがそのような例外をどのように検出し、対応するかを検討してください。通常、これには、例外をコールバックに転送し、例外を受け取るメソッドでスローされた例外を文書化することが含まれます。非同期例外は、アノテーション付きメソッドから実際に再スローされない限り、@throws でドキュメント化しないでください。

ドキュメントの最初の文をピリオドで終わらせる

Doclava ツールはドキュメントを単純に解析し、ピリオド(.)の後にスペースが続くとすぐに概要ドキュメント(クラスドキュメントの冒頭の簡単な説明で使用される最初の文)を終了します。これにより、次の 2 つの問題が発生します。

  • 短いドキュメントがピリオドで終わっていない場合、そのメンバーがツールで取得されるドキュメントを継承している場合、概要にもそれらの継承されたドキュメントが取得されます。たとえば、R.attr ドキュメントactionBarTabStyle をご覧ください。概要にディメンションの説明が追加されています。
  • 同様の理由で、最初の文で「e.g.」を使用しないでください。Doclava は「g.」の後に概要ドキュメントを終了するためです。たとえば、View.javaTEXT_ALIGNMENT_CENTER をご覧ください。Metalava は、ピリオドの後に改行なしスペースを挿入することでこのエラーを自動的に修正しますが、そもそもこのような間違いをしないようにしてください。

HTML でレンダリングされるようにドキュメントをフォーマットする

Javadoc は HTML でレンダリングされるため、ドキュメントの形式をそれに合わせてください。

  • 改行には明示的な <p> タグを使用する必要があります。終了 </p> タグは追加しないでください。

  • リストや表のレンダリングに ASCII を使用しないでください。

  • リストでは、順序なしリストには <ul>、順序付きリストには <ol> を使用します。各アイテムは <li> タグで始める必要がありますが、終了 </li> タグは必要ありません。最後の項目の後に終了タグ </ul> または </ol> が必要です。

  • テーブルでは、行に <table><tr>、ヘッダーに <th>、セルに <td> を使用する必要があります。すべてのテーブルタグには、対応する終了タグが必要です。任意のタグで class="deprecated" を使用して、非推奨を示すことができます。

  • インライン コードフォントを作成するには、{@code foo} を使用します。

  • コードブロックを作成するには、<pre> を使用します。

  • <pre> ブロック内のテキストはすべてブラウザによって解析されるため、角かっこ <> の使用には注意してください。&lt;&gt; の HTML エンティティでエスケープできます。

  • または、問題のあるセクションを {@code foo} で囲む場合は、コード スニペットに生の角かっこ <> を残すこともできます。次に例を示します。

    <pre>{@code <manifest>}</pre>
    

API リファレンス スタイルガイドに沿う

クラスの概要、メソッドの説明、パラメータの説明などの項目のスタイルを統一するため、Javadoc ツールのドキュメント コメントの記述方法にある Java 言語の公式ガイドラインの推奨事項に沿ってください。

Android Framework 固有のルール

これらのルールは、Android フレームワークに組み込まれた API と動作に固有の API、パターン、データ構造(BundleParcelable など)に関するものです。

インテント ビルダーは create*Intent() パターンを使用する必要があります

インテントのクリエイターは、createFooIntent() という名前のメソッドを使用する必要があります。

汎用データ構造を新たに作成する代わりに Bundle を使用する

任意のキーから型付きの値へのマッピングを表す新しい汎用データ構造を作成しないでください。代わりに Bundle の使用を検討してください。

これは通常、プラットフォーム以外のアプリとサービス間の通信チャネルとして機能するプラットフォーム API を作成する場合に発生します。この場合、プラットフォームはチャネル経由で送信されたデータを読み取らず、API コントラクトはプラットフォーム外で部分的に定義されている可能性があります(たとえば、Jetpack ライブラリ内など)。

プラットフォームがデータを読み取る場合は、Bundle を使用せず、厳密に型付けされたデータクラスを使用してください。

Parcelable 実装には公開 CREATOR フィールドが必要

Parcelable のインフレーションは、未加工のコンストラクタではなく CREATOR を通じて公開されます。クラスが Parcelable を実装する場合、その CREATOR フィールドもパブリック API でなければならず、Parcel 引数を取るクラス コンストラクタはプライベートでなければなりません。

UI 文字列に CharSequence を使用する

文字列をユーザー インターフェースに表示する場合は、CharSequence を使用して Spannable インスタンスを許可します。

キーや、ユーザーに表示されない他のラベルや値のみの場合は、String で問題ありません。

列挙型の使用を避ける

すべてのプラットフォーム API で列挙型ではなく IntDef を使用する必要があります。また、バンドルされていないライブラリ API でも使用することを強く推奨します。新しい値が追加されないことが確実な場合にのみ、列挙型を使用してください。

IntDef のメリット:

  • 時間の経過に伴う値の追加を有効にします。
    • プラットフォームで列挙型の値が追加されたために網羅的でなくなった場合、Kotlin の when ステートメントは実行時に失敗する可能性があります。
  • 実行時に使用されるクラスやオブジェクトはなく、プリミティブのみ
    • R8 または縮小化によって、バンドルされていないライブラリ API のこのコストを回避できますが、この最適化はプラットフォーム API クラスには影響しません。

列挙型のメリット

  • Java、Kotlin のイディオム的な言語機能
  • 網羅的な switch、when ステートメントの使用を有効にします
    • 注 - 値は時間とともに変化してはなりません。前のリストを参照してください
  • 明確なスコープと見つけやすい命名
  • コンパイル時の検証を有効にします。
    • たとえば、値を返す Kotlin の when ステートメント
  • インターフェースを実装し、静的ヘルパーを持ち、メンバー メソッドまたは拡張メソッドを公開し、フィールドを公開できる機能クラスです。

Android パッケージのレイヤリング階層に従う

android.* パッケージ階層には暗黙的な順序があり、下位レベルのパッケージは上位レベルのパッケージに依存できません。

Google や他の企業、その製品について言及することを避ける

Android プラットフォームはオープンソース プロジェクトであり、ベンダーに依存しないことを目指しています。API は汎用的で、必要な権限を持つシステム インテグレータやアプリが同様に使用できる必要があります。

Parcelable の実装は final にすべき

プラットフォームで定義された Parcelable クラスは常に framework.jar から読み込まれるため、アプリが Parcelable 実装をオーバーライドしようとすることは無効です。

送信側アプリが Parcelable を拡張する場合、受信側アプリには、送信側のカスタム実装をアンパックする手段がありません。下位互換性に関する注意: クラスがこれまで final ではなかったが、公開されているコンストラクタがない場合でも、final とマークできます。

システム プロセスを呼び出すメソッドは RemoteException を RuntimeException として再スローすべき

RemoteException は通常、内部 AIDL によってスローされ、システム プロセスが終了したか、アプリが過剰なデータを送信しようとしていることを示します。どちらの場合も、アプリがセキュリティやポリシーの決定を保持しないように、公開 API は RuntimeException として再スローする必要があります。

Binder 呼び出しの相手側がシステム プロセスであることがわかっている場合、このボイラープレート コードがベスト プラクティスです。

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

API の変更に対して特定の例外をスロー

公開 API の動作は API レベル間で変更される可能性があり、アプリのクラッシュを引き起こす可能性があります(たとえば、新しいセキュリティ ポリシーを適用するため)。

API が以前は有効だったリクエストに対してスローする必要がある場合は、一般的な例外ではなく、新しい特定の例外をスローします。たとえば、SecurityException ではなく ExportedFlagRequiredExportedFlagRequiredSecurityException を拡張できます)。

これにより、アプリ デベロッパーやツールが API の動作の変更を検出できるようになります。

クローンの代わりにコピー コンストラクタを実装

Object クラスによって提供される API 契約がないことと、clone() を使用するクラスの拡張に固有の難しさがあるため、Java の clone() メソッドの使用は強く推奨されません。代わりに、同じ型のオブジェクトを受け取るコピー コンストラクタを使用します。

/**
 * Constructs a shallow copy of {@code other}.
 */
public Foo(Foo other)

構築に Builder を使用するクラスでは、コピーの変更を可能にするために Builder コピー コンストラクタの追加を検討する必要があります。

public class Foo {
    public static final class Builder {
        /**
         * Constructs a Foo builder using data from {@code other}.
         */
        public Builder(Foo other)

FileDescriptor ではなく ParcelFileDescriptor を使用する

java.io.FileDescriptor オブジェクトのオーナー権限の定義が不十分であるため、use-after-close バグが発生する可能性があります。代わりに、API は ParcelFileDescriptor インスタンスを返すか受け取る必要があります。レガシー コードは、必要に応じて dup() または getFileDescriptor() を使用して PFD と FD の間で変換できます。

奇数の数値を使用しない

short または byte の値を直接使用しないでください。多くの場合、将来の API の進化を制限することになります。

BitSet の使用を避ける

java.util.BitSet は実装には適していますが、公開 API には適していません。変更可能で、高頻度のメソッド呼び出しに割り当てが必要であり、各ビットが何を表すかの意味論的な意味を提供しません。

高パフォーマンスのシナリオでは、@IntDef を使用して int または long を使用します。パフォーマンスが低いシナリオでは、Set<EnumType> を検討してください。未加工のバイナリデータの場合は、byte[] を使用します。

android.net.Uri を優先

android.net.Uri は、Android API の URI に推奨されるカプセル化です。

java.net.URI は URI の解析が厳しすぎるため使用しないでください。また、java.net.URL は等価性の定義が著しく壊れているため、絶対に使用しないでください。

@IntDef、@LongDef、@StringDef としてマークされたアノテーションを非表示にする

@IntDef@LongDef@StringDef としてマークされたアノテーションは、API に渡すことができる有効な定数のセットを示します。ただし、API 自体としてエクスポートされる場合、コンパイラは定数をインライン化し、アノテーションの API スタブ(プラットフォームの場合)または JAR(ライブラリの場合)には(無用になった)値のみが残ります。

そのため、これらのアノテーションの使用は、プラットフォームでは @hide ドキュメント アノテーション、ライブラリでは @RestrictTo.Scope.LIBRARY) コード アノテーションでマークする必要があります。API スタブや JAR に表示されないようにするには、どちらの場合も @Retention(RetentionPolicy.SOURCE) をマークする必要があります。

@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
  STREAM_TYPE_FULL_IMAGE_DATA,
  STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}

プラットフォーム SDK とライブラリ AAR をビルドする際、ツールはアノテーションを抽出し、コンパイル済みソースとは別にバンドルします。Android Studio はこのバンドル形式を読み取り、型定義を適用します。

新しい設定プロバイダ キーを追加しない

Settings.GlobalSettings.SystemSettings.Secure から新しいキーを公開しないでください。

代わりに、適切な getter と setter の Java API を関連するクラス(通常は「マネージャー」クラス)に追加します。必要に応じて、リスナー メカニズムまたはブロードキャストを追加して、クライアントに変更を通知します。

SettingsProvider 設定には、ゲッター/セッターと比較して、次のような問題があります。

  • 型安全性がありません。
  • デフォルト値を指定する統一された方法がない。
  • 権限をカスタマイズする適切な方法がない。
    • たとえば、カスタム権限で設定を保護することはできません。
  • カスタム ロジックを適切に追加する方法がない。
    • たとえば、設定 B の値に応じて設定 A の値を変更することはできません。

例: Settings.Secure.LOCATION_MODE は長い間存在していましたが、位置情報チームは適切な Java API LocationManager.isLocationEnabled()MODE_CHANGED_ACTION ブロードキャストのために、これを非推奨にしました。これにより、チームの柔軟性が大幅に向上し、API のセマンティクスがより明確になりました。

Activity と AsyncTask を拡張しない

AsyncTask は実装の詳細です。代わりに、リスナーまたは androidx の ListenableFuture API を公開します。

Activity サブクラスを構成することはできません。機能のアクティビティを拡張すると、ユーザーに同じ操作を求める他の機能との互換性がなくなります。代わりに、LifecycleObserver などのツールを使用してコンポジションに依存します。

Context の getUser() を使用する

Context にバインドされたクラス(Context.getSystemService() から返されたものなど)は、特定のユーザーをターゲットとするメンバーを公開するのではなく、Context にバインドされたユーザーを使用する必要があります。

class FooManager {
  Context mContext;

  void fooBar() {
    mIFooBar.fooBarForUser(mContext.getUser());
  }
}
class FooManager {
  Context mContext;

  Foobar getFoobar() {
    // Bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBarForUser(Process.myUserHandle());
  }

  Foobar getFoobar() {
    // Also bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBar();
  }

  Foobar getFoobarForUser(UserHandle user) {
    mIFooBar.fooBarForUser(user);
  }
}

例外: UserHandle.ALL など、単一のユーザーを表さない値を受け入れるメソッドは、ユーザー引数を受け入れる場合があります。

プレーンな整数ではなく UserHandle を使用

UserHandle は、型安全性を確保し、ユーザー ID と uid の混同を避けるために推奨されます。

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

避けられない場合、ユーザー ID を表す int には @UserIdInt のアノテーションを付ける必要があります。

Foobar getFoobarForUser(@UserIdInt int user);

ブロードキャスト インテントよりもリスナーまたはコールバックを優先する

ブロードキャスト インテントは非常に強力ですが、システムの健全性に悪影響を及ぼす可能性のある予期しない動作を引き起こすため、新しいブロードキャスト インテントは慎重に追加する必要があります。

以下に、新しいブロードキャスト インテントの導入を推奨しない具体的な懸念事項を示します。

  • FLAG_RECEIVER_REGISTERED_ONLY フラグなしでブロードキャストを送信すると、実行されていないアプリが強制的に起動されます。これは意図した結果である場合もありますが、数十ものアプリが同時に起動し、システムの健全性に悪影響を及ぼす可能性があります。さまざまな前提条件が満たされたときに調整を改善するには、JobScheduler などの代替戦略を使用することをおすすめします。

  • ブロードキャストを送信する場合、アプリに配信されるコンテンツをフィルタリングまたは調整する機能はほとんどありません。そのため、今後のプライバシーに関する懸念に対応したり、受信側アプリのターゲット SDK に基づいて動作の変更を導入したりすることが困難または不可能になります。

  • ブロードキャスト キューは共有リソースであるため、過負荷になる可能性があり、イベントがタイムリーに配信されないことがあります。10 分以上のエンドツーエンド レイテンシを持つブロードキャスト キューがいくつか確認されています。

こうした理由から、新しい機能ではブロードキャスト インテントではなく、リスナーやコールバック、JobScheduler などの他の機能の使用を検討することをおすすめします。

ブロードキャスト インテントが依然として理想的な設計である場合は、次のベスト プラクティスを考慮する必要があります。

  • 可能であれば、Intent.FLAG_RECEIVER_REGISTERED_ONLY を使用して、ブロードキャストをすでに実行中のアプリに制限します。たとえば、ACTION_SCREEN_ON はこの設計を使用してアプリの起動を回避します。
  • 可能であれば、Intent.setPackage() または Intent.setComponent() を使用して、ブロードキャストの対象を特定の対象アプリに設定します。たとえば、ACTION_MEDIA_BUTTON はこの設計を使用して、再生コントロールを処理する現在のアプリにフォーカスします。
  • 可能であれば、ブロードキャストを <protected-broadcast> として定義し、悪意のあるアプリが OS を偽装するのを防ぎます。

システム バインド デベロッパー サービスのインテント

デベロッパーによる拡張を目的とし、システムによってバインドされるサービス(NotificationListenerService などの抽象サービスなど)は、システムからの Intent アクションに応答する場合があります。このようなサービスは、次の条件を満たす必要があります。

  1. サービスの完全修飾クラス名を含むクラスに SERVICE_INTERFACE 文字列定数を定義します。この定数には @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) アノテーションを付ける必要があります。
  2. プラットフォームからインテントを受け取るために、デベロッパーが AndroidManifest.xml<intent-filter> を追加する必要があるクラスに関するドキュメント。
  3. 不正なアプリがデベロッパー サービスに Intent を送信するのを防ぐために、システムレベルの権限を追加することを強く推奨します。

Kotlin-Java 相互運用

ガイドラインの完全なリストについては、Android の公式の Kotlin-Java 相互運用ガイドをご覧ください。見つけやすさを向上させるため、選択したガイドラインがこのガイドにコピーされました。

API の公開設定

suspend fun などの一部の Kotlin API は、Java デベロッパーが使用することを想定していません。ただし、@JvmSynthetic を使用して言語固有の可視性を制御しようとしないでください。デバッガでの API の表示方法に副作用が生じ、デバッグが難しくなります。

具体的なガイダンスについては、Kotlin-Java 相互運用ガイドまたは非同期ガイドをご覧ください。

コンパニオン オブジェクト

Kotlin では、companion object を使用して静的メンバーを公開します。場合によっては、これらの値は、包含クラスではなく、Companion という名前の内部クラスの Java から表示されます。Companion クラスは API テキスト ファイルで空のクラスとして表示されることがありますが、これは想定どおりの動作です。

Java との互換性を最大限に高めるには、コンパニオン オブジェクトの定数以外のフィールド@JvmField公開関数@JvmStatic のアノテーションを付けて、それらを包含クラスで直接公開します。

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Android プラットフォーム API の進化

このセクションでは、既存の Android API にどのような変更を加えることができるか、また、既存のアプリやコードベースとの互換性を最大限に高めるために、それらの変更をどのように実装すべきかに関するポリシーについて説明します。

バイナリ互換性を損なう変更

最終版の公開 API サーフェスでバイナリの互換性を破る変更を避けます。このような変更は通常、make update-api の実行時にエラーを発生させますが、Metalava の API チェックで検出されないエッジケースが存在する可能性があります。不明な場合は、Eclipse Foundation の Evolving Java-based APIs ガイドで、Java で互換性のある API 変更の種類について詳しく説明しています。非公開(システムなど)API のバイナリ互換性を破る変更は、非推奨/置き換えサイクルに沿って行う必要があります。

ソースの互換性を損なう変更

バイナリの互換性を破る変更でなくても、ソースの互換性を破る変更は推奨されません。バイナリ互換だがソース互換性がない変更の例として、既存のクラスにジェネリックを追加することが挙げられます。これはバイナリ互換ですが、継承や曖昧な参照によりコンパイル エラーが発生する可能性があります。ソースを破壊する変更は make update-api の実行時にエラーを発生させないため、既存の API シグネチャに対する変更の影響を理解するように注意する必要があります。

場合によっては、デベロッパー エクスペリエンスやコードの正確性を向上させるために、ソースを破壊する変更が必要になることがあります。たとえば、Java ソースに null 可能性アノテーションを追加すると、Kotlin コードとの相互運用性が向上し、エラーの可能性が低くなりますが、多くの場合、ソースコードに大幅な変更が必要になります。

非公開 API の変更

@TestApi でアノテーションされた API はいつでも変更できます。

@SystemApi でアノテーションが付けられた API は 3 年間保持する必要があります。次のスケジュールでシステム API を削除またはリファクタリングする必要があります。

  • API y - 追加
  • API y+1 - サポート終了
    • コードに @Deprecated のマークを付けます。
    • 置換を追加し、@deprecated ドキュメント アノテーションを使用して、非推奨のコードの Javadoc で置換にリンクします。
    • 開発サイクルの間に、API が非推奨になることを内部ユーザーに伝えるバグを報告します。これにより、代替 API が適切であることを検証できます。
  • API y+2 - ソフト削除
    • コードに @removed のマークを付けます。
    • 必要に応じて、リリース用の現在の SDK レベルをターゲットとするアプリに対して例外をスローするか、何もしないようにします。
  • API y+3 - 完全削除
    • ソースツリーからコードを完全に削除します。

サポートの終了

非推奨は API の変更と見なされ、メジャー(文字など)リリースで発生する可能性があります。API を非推奨にする場合は、@Deprecated ソース アノテーションと @deprecated <summary> ドキュメント アノテーションを一緒に使用します。要約には移行戦略を含める必要があります。この戦略では、代替 API へのリンクが示されたり、API を使用すべきでない理由が説明されたりする場合があります。

/**
 * Simple version of ...
 *
 * @deprecated Use the {@link androidx.fragment.app.DialogFragment}
 *             class with {@link androidx.fragment.app.FragmentManager}
 *             instead.
 */
@Deprecated
public final void showDialog(int id)

また、XML で定義され Java で公開されている API(android.R クラスで公開されている属性とスタイル設定可能なプロパティを含む)も、次の概要とともに非推奨にしなければなりません

<!-- Attribute whether the accessibility service ...
     {@deprecated Not used by the framework}
 -->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />

API を非推奨にするタイミング

非推奨は、新しいコードで API の採用を推奨しない場合に最も有用です。

また、API を @removed にする前に @deprecated としてマークすることも義務付けていますが、これでは、すでに使用している API から移行する強い動機をデベロッパーに与えることはできません。

API を非推奨にする前に、デベロッパーへの影響を考慮してください。API の非推奨化による影響は次のとおりです。

  • javac はコンパイル時に警告を発行します。
    • 非推奨の警告はグローバルに抑制したり、ベースラインに設定したりできないため、-Werror を使用するデベロッパーは、コンパイル SDK バージョンを更新する前に、非推奨の API の使用箇所をすべて個別に修正または抑制する必要があります。
    • 非推奨のクラスのインポートに関する非推奨の警告を抑制できません。そのため、コンパイル SDK バージョンを更新する前に、非推奨のクラスのすべての使用箇所で完全修飾クラス名をインライン化する必要があります。
  • d.android.com のドキュメントに非推奨の通知が表示されます。
  • Android Studio などの IDE では、API の使用箇所に警告が表示されます。
  • IDE で API のランクが下げられたり、自動補完で非表示になったりする可能性があります。

その結果、API の非推奨化は、コードの健全性を最も重視するデベロッパー(-Werror を使用するデベロッパー)が新しい SDK を採用することを妨げる可能性があります。既存のコードの警告を気にしないデベロッパーは、非推奨を完全に無視する可能性があります。

多数の非推奨を導入する SDK は、どちらの場合も悪化させます。

そのため、API の非推奨化は次の場合にのみ行うことをおすすめします。

  • この API は今後のリリースで @remove される予定です。
  • API の使用により、互換性を損なうことなく修正できない、正しくない動作や未定義の動作が発生する。

API を非推奨にして新しい API に置き換える場合は、古いデバイスと新しいデバイスの両方のサポートを簡素化するために、対応する互換性 API を androidx.core などの Jetpack ライブラリに追加することを強くおすすめします

現在および将来のリリースで意図したとおりに動作する API を非推奨にすることはおすすめしません

/**
 * ...
 * @deprecated Use {@link #doThing(int, Bundle)} instead.
 */
@Deprecated
public void doThing(int action) {
  ...
}

public void doThing(int action, @Nullable Bundle extras) {
  ...
}

API がドキュメント化された動作を維持できなくなった場合は、非推奨が適切です。

/**
 * ...
 * @deprecated No longer displayed in the status bar as of API 21.
 */
@Deprecated
public RemoteViews tickerView;

非推奨 API の変更

非推奨の API の動作を維持する必要があります。つまり、テスト実装は変更されず、API を非推奨にした後もテストは合格し続ける必要があります。API にテストがない場合は、テストを追加する必要があります。

今後のリリースで非推奨の API サーフェスを拡張しない。既存の非推奨 API に lint の正しさに関するアノテーション(@Nullable など)を追加することはできますが、新しい API を追加することはできません。

新しい API を非推奨として追加しないでください。API が追加され、プレリリース サイクル内で非推奨になった場合(つまり、最初に非推奨として公開 API サーフェスに入った場合)、API を確定する前に削除する必要があります

ソフト削除

ソフト削除はソースを破壊する変更であり、API Council が明示的に承認しない限り、公開 API で避けるべきです。システム API の場合、ソフト削除の前に、メジャー リリースの期間だけ API を非推奨にする必要があります。API へのドキュメント参照をすべて削除し、API を削除するときは @removed <summary> ドキュメント アノテーションを使用します。概要には、削除の理由を含める必要があります。また、非推奨で説明したように、移行戦略を含めることもできます。

削除された API の動作はそのまま維持できますが、既存の呼び出し元が API を呼び出したときにクラッシュしないように維持することが重要です。場合によっては、動作を維持することを意味します。

テスト カバレッジは維持しなければなりませんが、動作の変更に対応するためにテストの内容を変更する必要がある場合があります。テストでは、既存の呼び出し元が実行時にクラッシュしないことを検証する必要があります。削除された API の動作をそのまま維持できますが、既存の呼び出し元が API を呼び出したときにクラッシュしないように維持することが重要です。場合によっては、動作を維持することを意味します。

テスト カバレッジを維持する必要がありますが、動作の変更に対応するためにテストの内容を変更する必要がある場合があります。テストでは、既存の呼び出し元が実行時にクラッシュしないことを検証する必要があります。

技術的には、@remove Javadoc アノテーションを使用して、SDK スタブ JAR とコンパイル時のクラスパスから API を削除しますが、実行時のクラスパスには引き続き存在します。これは @hide API と同様です。

/**
 * Ringer volume. This is ...
 *
 * @removed Not functional since API 2.
 */
public static final String VOLUME_RING = ...

アプリ デベロッパーの観点から見ると、API はオートコンプリートに表示されなくなり、API を参照するソースコードは、compileSdk が API が削除された SDK 以降の SDK と等しい場合、コンパイルされなくなります。ただし、ソースコードは以前の SDK に対して引き続き正常にコンパイルされ、API を参照するバイナリは引き続き動作します。

特定のカテゴリの API は、削除(復元可能)してはなりません。特定のカテゴリの API を削除することはできません

抽象メソッド

デベロッパーが拡張する可能性のあるクラスの抽象メソッドを削除(復元可能)してはなりません。これにより、デベロッパーはすべての SDK レベルでクラスを正常に拡張できなくなります。

開発者がクラスを拡張することが一度もなく、今後もないことがまれにあります。そのような場合でも、抽象メソッドをソフト削除できます。

削除(復元不可)

ハード削除はバイナリの破壊的変更であり、公開 API で発生してはなりません。

推奨されないアノテーション

@Discouraged アノテーションは、ほとんどの場合(95% 超)に API を推奨しないことを示すために使用します。非推奨の API は、非推奨を回避する狭い範囲の重要なユースケースが存在するという点で、非推奨の API とは異なります。API を非推奨としてマークする場合は、説明と代替ソリューションを提供する必要があります。

@Discouraged(message = "Use of this function is discouraged because resource
                        reflection makes it harder to perform build
                        optimizations and compile-time verification of code. It
                        is much more efficient to retrieve resources by
                        identifier (such as `R.foo.bar`) than by name (such as
                        `getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

新しい API は非推奨のため、追加しないでください

既存の API の動作の変更

既存の API の実装動作を変更したい場合があります。たとえば、Android 7.0 では、デベロッパーが Binder を介して送信するには大きすぎるイベントを投稿しようとしたときに、明確に通知するように DropBoxManager を改善しました。

ただし、既存のアプリで問題が発生しないように、古いアプリの安全な動作を維持することを強くおすすめします。これまで、これらの動作変更はアプリの ApplicationInfo.targetSdkVersion に基づいて保護されてきましたが、最近、アプリ互換性フレームワークの使用を必須とするように移行しました。この新しいフレームワークを使用して動作の変更を実装する方法の例を次に示します。

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;

public class MyClass {
  @ChangeId
  // This means the change will be enabled for target SDK R and higher.
  @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
  // Use a bug number as the value, provide extra detail in the bug.
  // FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
  static final long FOO_NOW_DOES_X = 123456789L;

  public void doFoo() {
    if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
      // do the new thing
    } else {
      // do the old thing
    }
  }
}

このアプリ互換性フレームワークの設計により、デベロッパーは、プレビュー版とベータ版のリリース中に、アプリのデバッグの一環として特定の動作変更を一時的に無効にできます。これにより、数十もの動作変更に同時に対応する必要がなくなります。

上位互換性

上位互換性とは、システムがそれ自身の後のバージョンを対象とした入力を受け入れられるようにする設計特性です。API 設計の場合、デベロッパーはコードを 1 回記述して 1 回テストすれば、問題なくどこでも実行できることを期待しているため、初期設計と将来の変更に特に注意する必要があります。

Android で最も一般的な前方互換性の問題を引き起こすのは、次のものです。

  • 以前は完了していると想定されていたセット(@IntDefenum など)に新しい定数を追加した場合(たとえば、switch に例外をスローする default がある場合)。
  • API サーフェスで直接キャプチャされない機能のサポートを追加(たとえば、以前は <color> リソースのみがサポートされていた XML での ColorStateList 型リソースの割り当てのサポート)。
  • ランタイム チェックの制限を緩和する(下位バージョンに存在していた requireNotNull() チェックを削除するなど)。

このような場合、デベロッパーは実行時に初めて問題があることに気づきます。さらに悪いことに、フィールドの古いデバイスからのクラッシュ レポートの結果として判明する可能性もあります。

また、これらのケースはすべて、技術的には有効な API 変更です。バイナリやソースの互換性を損なうことはなく、API lint でこれらの問題が検出されることもありません。

そのため、API 設計者は既存のクラスを変更する際に注意する必要があります。「この変更により、プラットフォームの最新バージョンに対してのみ記述およびテストされたコードが、以前のバージョンで失敗するようになるか?」という質問をします。

XML スキーマ

XML スキーマがコンポーネント間の安定したインターフェースとして機能する場合、他の Android API と同様に、そのスキーマを明示的に指定し、下位互換性を持たせる必要があります。たとえば、XML 要素と属性の構造は、他の Android API サーフェスでメソッドと変数が維持されるのと同様に維持する必要があります。

XML のサポート終了

XML 要素または属性を非推奨にする場合は、xs:annotation マーカーを追加できますが、通常の @SystemApi 進化ライフサイクルに従って、既存の XML ファイルのサポートを継続する必要があります。

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

要素の型は保持する必要がある

スキーマは、complexType 要素の子要素として sequence 要素、choice 要素、all 要素をサポートしています。ただし、これらの子要素は子要素の数と順序が異なるため、既存の型を変更すると互換性のない変更になります。

既存の型を変更する場合は、古い型を非推奨にして、それを置き換える新しい型を導入することをおすすめします。

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

Mainline 固有のパターン

Mainline は、システム イメージ全体を更新するのではなく、Android OS のサブシステム(「Mainline モジュール」)を個別に更新できるようにするプロジェクトです。

Mainline モジュールはコア プラットフォームから「分離」される必要があります。つまり、各モジュールと他の部分とのやり取りはすべて、正式な(公開またはシステム)API を使用して行われる必要があります。

メインライン モジュールが従うべき特定の設計パターンがあります。このセクションでは、それらについて説明します。

<Module>FrameworkInitializer パターン

メインライン モジュールで @SystemService クラス(JobScheduler など)を公開する必要がある場合は、次のパターンを使用します。

  • モジュールから <YourModule>FrameworkInitializer クラスを公開します。このクラスは $BOOTCLASSPATH に存在する必要があります。例: StatsFrameworkInitializer

  • @SystemApi(client = MODULE_LIBRARIES) でマークします。

  • public static void registerServiceWrappers() メソッドを追加します。

  • Context への参照が必要な場合は、SystemServiceRegistry.registerContextAwareService() を使用してサービス マネージャー クラスを登録します。

  • Context への参照が必要ない場合は、SystemServiceRegistry.registerStaticService() を使用してサービス マネージャー クラスを登録します。

  • SystemServiceRegistry の静的イニシャライザから registerServiceWrappers() メソッドを呼び出します。

<Module>ServiceManager パターン

通常、システム サービス バインダ オブジェクトを登録したり、それらへの参照を取得したりするには ServiceManager を使用しますが、メインライン モジュールは非表示になっているため、これを使用できません。メインライン モジュールは、静的プラットフォームや他のモジュールによって公開されるシステム サービス バインダ オブジェクトを登録または参照しないため、このクラスは非表示になっています。

Mainline モジュールは、代わりに次のパターンを使用して、モジュール内で実装されたバインダ サービスへの参照を登録して取得できます。

  • TelephonyServiceManager の設計に沿って <YourModule>ServiceManager クラスを作成します。

  • クラスを @SystemApi として公開します。$BOOTCLASSPATH クラスまたはシステム サーバー クラスからのみアクセスする必要がある場合は、@SystemApi(client = MODULE_LIBRARIES) を使用できます。それ以外の場合は、@SystemApi(client = PRIVILEGED_APPS) を使用します。

  • このクラスは以下で構成されます。

    • コンストラクタが非表示になっているため、静的プラットフォーム コードのみがインスタンス化できます。
    • 特定の名前の ServiceRegisterer インスタンスを返すパブリック ゲッター メソッド。バインダ オブジェクトが 1 つある場合は、ゲッター メソッドが 1 つ必要です。2 つある場合は、2 つのゲッターが必要です。
    • ActivityThread.initializeMainlineModules() で、このクラスをインスタンス化し、モジュールによって公開される静的メソッドに渡します。通常、FrameworkInitializer クラスに、それを受け取る静的 @SystemApi(client = MODULE_LIBRARIES) API を追加します。

このパターンでは、get() API と register() API は他のメインライン モジュールから見えるものの、他のモジュールが <YourModule>ServiceManager のインスタンスを取得する方法がないため、他のメインライン モジュールがこれらの API にアクセスできなくなります。

テレフォニーがテレフォニー サービスへの参照を取得する方法については、コード検索リンクをご覧ください。

ネイティブ コードでサービス バインダ オブジェクトを実装する場合は、AServiceManager ネイティブ API を使用します。これらの API は ServiceManager Java API に対応していますが、ネイティブ API はメインライン モジュールに直接公開されています。モジュールが所有していないバインダ オブジェクトを登録または参照するために使用しないでください。ネイティブからバインダ オブジェクトを公開する場合、<YourModule>ServiceManager.ServiceRegistererregister() メソッドは必要ありません。

Mainline モジュールの権限の定義

APK を含む Mainline モジュールは、通常の APK と同様に、APK の AndroidManifest.xml で(カスタム)権限を定義できます。

定義された権限がモジュール内で内部的にのみ使用される場合、その権限名には APK パッケージ名を接頭辞として付加する必要があります(例:)。

<permission
    android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
    android:protectionLevel="signature" />

定義された権限が、更新可能なプラットフォーム API の一部として他のアプリに提供される場合、その権限名には「android.permission.」という接頭辞を付ける必要があります。(静的プラットフォーム権限と同様)にモジュール パッケージ名を加えたもの。モジュールからのプラットフォーム API であることを示し、名前の競合を回避します。例:

<permission
    android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
    android:label="@string/active_calories_burned_read_content_description"
    android:protectionLevel="dangerous"
    android:permissionGroup="android.permission-group.HEALTH" />

その後、モジュールは、この権限名を API サーフェスで API 定数(HealthPermissions.READ_ACTIVE_CALORIES_BURNED など)として公開できます。