Android API ガイドライン

このページは、API 審査で API 評議会が適用する一般的な原則をデベロッパーが理解するためのガイドとして作成されています。

デベロッパーは、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 評議会に連絡してください。

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 設計者は、ラムダとしての使用を簡素化するために、1 つのメソッドを抽象のままにすることができます。

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 インターフェースが実装の詳細である場合、将来自由に変更できます。公開レイヤにより、必要な下位互換性を維持できます。たとえば、内部呼び出しに新しい引数を追加したり、バッチ処理やストリーミング、共有メモリなどを使用して 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 つは、Binder または IBinder をトークンとして使用することであり、これは ID セマンティクスがあるためです。未加工の 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 には一部の 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 Compose の実装や、必要に応じて Jetpack ライブラリでデベロッパー向けのビューベースの UI コンポーネントの実装に使用できる低レベルの API として公開する必要があります。ライブラリでこれらのコンポーネントを提供すると、プラットフォーム機能を利用できない場合にバックポートされた実装を行うことができます。

フィールド

これらのルールは、クラスのパブリック フィールドに関するものです。

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

Java クラスは、フィールドを直接公開しないでください。フィールドはプライベートにし、これらのフィールドが final かどうかにかかわらず、公開ゲッターとセッターを使用してのみアクセスできるようにする必要があります。

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

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

公開されるフィールドには最終的なマークを付ける必要があります

元のフィールドは使用しないことを強くおすすめします(元のフィールドを公開しないをご覧ください)。ただし、フィールドが公開フィールドとして公開されるまれな状況では、そのフィールドを 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 オープンソース プロジェクト用に予約されています。

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

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 の公開フィールドと同様に、キャメルケースの命名規則(@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 動作の変更を処理するのは複雑でエラーが発生しやすいためです。

整数定数または文字列定数

値の名前空間がパッケージの外部で拡張できない場合は、整数定数と @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 パラメータまたは戻り値で時間を表現する場合は、これらの型を使用することをおすすめします。

java.time.Duration または java.time.Instant を受け入れるまたは返す API のバリアントのみを公開し、同じユースケースのプリミティブ バリアントを省略することをおすすめします。ただし、API ドメインが、想定される使用パターンでオブジェクトの割り当てがパフォーマンスに悪影響を及ぼす可能性がある場合は除きます。

時間の長さを表すメソッドの名前は 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() 時間ベースの正のタイムスタンプです。

測定単位

時間以外の測定単位を表現するすべてのメソッドでは、SI 単位の接頭辞を CamelCase で記述することをおすすめします。

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 相互運用ガイドのデフォルトの関数オーバーロードをご覧ください。

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

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

ビルダー

複雑な Java オブジェクトを作成する場合は、Builder パターンを使用することをおすすめします。このパターンは、次のような場合に 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() を除くすべてのメソッドからビルダー オブジェクト(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 は公開する必要があります。ライブラリは、@PublishedApi internal を使用して Kotlin クライアントから Builder クラスのコンストラクタを選択的に非表示にしてはなりません

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 の使用を検討してください)。

ビルダークラスは、ビルドされた型の最終的な静的内部クラスである必要があります。

パッケージ内の論理的な編成のために、ビルダー クラスは通常、ビルドタイプの最終的な内部クラスとして公開する必要があります(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 引数を取ることができる

2 次入力には、特に Kotlin で、ビルダーやオーバーロードではなくデフォルト引数を使用するため、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() のスタイルを使用する必要があります。

Builder クラスは build() メソッドを宣言することが期待されます

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

ビルダーの 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 が追加されず、プロパティ名自体がゲッターとして使用されます。そのため、命名ガイドラインに従うには、Boolean プロパティに is 接頭辞を付けることをおすすめします

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 を使用するをご覧ください。

Setter

2 つの setter メソッドを指定する必要があります。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 では、常に publicprotected よりも優先してください。保護されたアクセスは、デフォルトで外部アクセスで十分な場合でも、実装者がオーバーライドして公開アクセサタを提供する必要があるため、長期的には面倒になります。

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 は、InputStream または OutputStream の両方を受け入れることも必要です。

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)

プリミティブ型のクラス同等物を使用しないと、これらのクラスのメモリ オーバーヘッド、値へのメソッド アクセス、さらに重要なことに、プリミティブ型とオブジェクト型間のキャスティングによる自動ボックス化を回避できます。このような動作を回避すると、メモリと一時割り当てを節約できます。これらの割り当てにより、高コストで頻繁なガベージ コレクションが発生する可能性があります。

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

さまざまな状況で許容される値を明確にするために、デベロッパー アノテーションが追加されました。これにより、ツールは、開発者が間違った値を指定した場合(フレームワークが特定の一連の定数値のいずれかを必要としているときに任意の int を渡す場合など)に、開発者を簡単にサポートできます。必要に応じて、次のアノテーションをすべて使用します。

null 可能性

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

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

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull: 指定された戻り値、パラメータ、フィールドが null 値にできないことを示します。@Nullable としてマークすることは Android では比較的新しいため、Android の API メソッドのほとんどは一貫してドキュメント化されていません。そのため、3 つの状態(「不明、@Nullable@NonNull」)があり、@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 では、インターフェース メソッドを実装するときに適切なアノテーションを追加する必要があります(つまり、実装クラスのメソッドは writeToParcel(Parcel, int) ではなく writeToParcel(@NonNull Parcel, int) にする必要があります)。ただし、アノテーションがない既存の API を「修正」する必要はありません。Parcelable.writeToParcel()

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 定数用です。複数の「接頭辞」値を含めることができます。これらの値は、すべての値のドキュメントを自動的に生成するために使用されます。

SDK 定数の場合は @SdkConstant

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

@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 可能性アノテーションが一致している必要があります

1 つの論理プロパティの 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();
}

戻り値またはパラメータの型として配列ではなくコレクション型を使用する

ジェネリック型のコレクション インターフェースには、一意性と順序付けに関する強力な 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 のデフォルトでは、変更可能な戻り型を優先する必要があります。これは、Java API の Android プラットフォーム実装で、まだ不変コレクションの便利な実装が提供されていないためです。このルールの例外は、変更不可の 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 で公開しないでください。いずれの場合も、同時アクセスの管理には細心の注意を払ってください。

vararg パラメータ型の使用

Kotlin と 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> を使用します。

  • Collection<Foo>,: API が順序に関係なく重複を許可する場合。

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 はラップされたコールバックのこのような変更を許可するようにビルドされています。

エグゼキュータを受け入れ、コールバックのディスパッチを制御する

明示的なスレッド処理が想定されていないコールバックを登録する場合(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()

ハンドラではなくエグゼキュータを使用する

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

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

このガイドラインの例外はまれです。例外を申請する一般的な理由は次のとおりです。

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

アプリコードでイベントを公開するスレッドをブロックしたくない。通常、この例外リクエストは、アプリプロセスで実行されるコードに対しては許可されません。このことを誤って判断したアプリは、自分自身にのみ悪影響を及ぼし、システム全体の健全性には影響しません。適切に処理されているアプリや、一般的な同時実行フレームワークを使用しているアプリは、追加のレイテンシのペナルティを支払う必要はありません。

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

登録時の対称性

何かを追加または登録する方法がある場合は、削除または登録解除する方法も用意する必要があります。メソッド

registerThing(Thing)

一致する

unregisterThing(Thing)

リクエスト ID を指定する

デベロッパーがコールバックを再利用する場合は、コールバックをリクエストに関連付ける 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 が返す結果は、指定された executor で呼び出されることにより、requestFooAsynccallback パラメータの OutcomeReceiver.onResult に報告されます。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 ターゲットの方が速い場合があります。

{@code foo} を使用して Java 値を区別する

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 をご覧ください。このドキュメントでは、ディメンションの説明が概要に追加されています。
  • 同じ理由で、最初の文に「例」を含めないでください。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> ブロック内のすべてのテキストはブラウザによって解析されるため、角かっこ <> には注意してください。これらの文字は、HTML エンティティ &lt;&gt; でエスケープできます。

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

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

API リファレンスのスタイルガイドに準拠する

クラスの概要、メソッドの説明、パラメータの説明などのスタイルの一貫性を保つには、Javadoc ツールのドキュメント コメントの記述方法の公式の Java 言語ガイドラインの推奨事項に従ってください。

Android フレームワーク固有のルール

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

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

インテントの作成者は、createFooIntent() という名前のメソッドを使用する必要があります。

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

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

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

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

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

Parcelable インフレーションは、元のコンストラクタではなく CREATOR を介して公開されます。クラスが Parcelable を実装する場合、その CREATOR フィールドもパブリック API にする必要があります。また、Parcel 引数を受け取るクラスのコンストラクタはプライベートにする必要があります。

UI 文字列に CharSequence を使用する

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

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

列挙型の使用は避ける

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

IntDef のメリット:

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

列挙型のメリット

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

Android パッケージの階層に従う

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

Google や他の企業、およびその製品に言及しない

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

Parcelable の実装は最終的なものである必要があります

プラットフォームで定義された 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 ではなく ExportedFlagRequired です(ExportedFlagRequiredSecurityException を拡張できます)。

これにより、アプリ デベロッパーとツールが 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 には適していません。変更可能で、頻繁なメソッド呼び出しの割り当てが必要であり、各ビットが表す意味的な意味は提供されません。

高パフォーマンスのシナリオでは、@IntDefint または 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 の新しいキーを公開しないでください。

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

SettingsProvider 設定には、ゲッター/セッターと比較していくつかの問題があります。

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

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

Activity と AsyncTask を拡張しない

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

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

Context の getUser() を使用する

Context.getSystemService() から返されるものなど、Context にバインドされたクラスは、特定のユーザーをターゲットとするメンバーを公開するのではなく、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 など、単一のユーザーを表さない値を受け取る場合、ユーザー引数を受け取ることができます。

単純な int ではなく 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 を使用して静的メンバーを公開します。場合によっては、Java から、包含クラスではなく Companion という名前の内部クラスに表示されます。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(システム 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 レベルをターゲットとするアプリに対してスローまたは no-op を実行します。
  • 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 を使用するデベロッパーは、非推奨の API の使用を個別に修正するか、すべて抑制してから、コンパイル SDK バージョンを更新する必要があります。
    • 非推奨のクラスのインポートに関する非推奨警告を抑制することはできません。そのため、デベロッパーは、非推奨のクラスをすべて使用する際に、コンパイル 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 評議会が明示的に承認しない限り、パブリック 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 が自動補完に表示されなくなり、compileSdk が API が削除された SDK 以降の場合、API を参照するソースコードはコンパイルされなくなります。ただし、以前の 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 では DropBoxManager を改善し、デベロッパーが Binder を介して送信できないほど大きいイベントを投稿しようとしたときに、明確に通知されるようにしました。

ただし、既存のアプリで問題が発生しないように、古いアプリの安全な動作を維持することを強くおすすめします。これまでは、アプリの 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 マーカーを追加できますが、既存の XML ファイルは、一般的な @SystemApi の進化ライフサイクルに沿って引き続きサポートする必要があります。

<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 を使用して行う必要があります。

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

<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() で、このクラスをインスタンス化し、モジュールで公開されている静的メソッドに渡します。通常は、それを受け取る静的 @SystemApi(client = MODULE_LIBRARIES) API を FrameworkInitializer クラスに追加します。

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

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

ネイティブ コードでサービス バインダー オブジェクトを実装する場合は、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 など)。