アクティビティのライフサイクル

ユーザーがアプリの内外を移動してからアプリに戻ると、アプリ内の Activity インスタンスはライフサイクルのさまざまな状態の間を遷移します。 Activity クラスには、状態が変化した場合、またはシステムがアクティビティの作成、停止、再開、またはアクティビティが存在するプロセスの破棄をアクティビティに通知するためのコールバックがいくつか用意されています。

ライフサイクル コールバック メソッド内で、ユーザーがアクティビティを離れてから戻った場合のアクティビティの動作を宣言できます。たとえば、ストリーミング動画プレーヤーを作成している場合、ユーザーが別のアプリに切り替えたときに動画を一時停止し、ネットワーク接続を終了できます。ユーザーが戻ったら、ネットワークに再接続して、同じ場所から動画を再開できるようにします。

各コールバックを使用すると、状態の変化に応じて適切な処理を実行できます。適切な作業を適切なタイミングで行い、移行を正しく処理することで、アプリの堅牢性とパフォーマンスが向上します。 たとえば、ライフサイクル コールバックを適切に実装すると、アプリで次の事態を回避できます。

  • アプリの使用中にユーザーが電話を受信したり、別のアプリに切り替えたりするとクラッシュする。
  • ユーザーが実際に使用していない場合に、貴重なシステム リソースが消費される。
  • ユーザーがアプリを離れてから後で戻ると、ユーザーの進捗状況が失われる。
  • 画面が横向きと縦向きの表示を切り替えている間に、クラッシュする、またはユーザーの進捗状況が失われる。

このドキュメントでは、アクティビティのライフサイクルについて詳しく説明します。最初にライフサイクル パラダイムについて説明します。次に、各コールバックについて、実行中の内部で行われる処理と、その間に実装する必要がある処理について説明します。

続いて、アクティビティの状態とシステムの強制終了に対するプロセスの脆弱性との関係を簡単に紹介します。 最後に、アクティビティの状態間の遷移に関連するトピックについて説明します。

ベスト プラクティスのガイダンスなど、ライフサイクルの処理の詳細については、 ライフサイクル対応コンポーネントによるライフサイクルの処理UI の状態を保存するをご覧ください。アクティビティとアーキテクチャ コンポーネントを組み合わせて使用し、製品版の品質を備えた堅牢なアプリを設計する方法については、アプリ アーキテクチャ ガイドをご覧ください。

アクティビティのライフサイクルに関するコンセプト

アクティビティのライフサイクルのステージ間の遷移をナビゲートするために、Activity クラスには 6 つのコールバック(onCreate()onStart()onResume()onPause()onStop()onDestroy())のコアセットが用意されています。アクティビティが新しい状態になると、これらのコールバックがそれぞれ呼び出されます。

図 1 は、このパラダイムを視覚的に表したものです。

図 1. アクティビティのライフサイクルに関する簡略な図

ユーザーがアクティビティから離れる処理を開始すると、システムはアクティビティを解体するメソッドを呼び出します。ユーザーが別のアプリに切り替えたときなど、アクティビティは部分的にのみ分離されてメモリ内に存在することがあります。そのような場合でも、アクティビティはフォアグラウンドに戻ることができます。

ユーザーがアクティビティに戻ると、前回中断したところから再開します。いくつかの例外を除き、アプリはバックグラウンドで実行中のアクティビティを開始できません

システムが特定のプロセスを強制終了する可能性と、そのプロセス内のアクティビティは、そのときのアクティビティの状態によって異なります。状態と排出に対する脆弱性の関係については、アクティビティの状態とメモリからの排出に関するセクションをご覧ください。

アクティビティの複雑さにもよりますが、すべてのライフサイクル メソッドを実装する必要はないと考えられます。ただし、それぞれを理解し、アプリがユーザーの期待どおりに動作するように実装することが重要です。

ライフサイクル コールバック

このセクションでは、アクティビティのライフサイクルで使用されるコールバック メソッドのコンセプトと実装について説明します。

一部のアクションはアクティビティのライフサイクル メソッドに属します。ただし、依存するコンポーネントのアクションを実装するコードは、アクティビティのライフサイクル メソッドではなく、コンポーネントに配置します。これを実現するには、依存コンポーネントをライフサイクル対応にする必要があります。依存するコンポーネントをライフサイクル対応にする方法については、 ライフサイクル対応コンポーネントによるライフサイクルへの対応をご覧ください。

onCreate()

このコールバックを実装する必要があります。このコールバックは、システムが最初にアクティビティを作成したときに呼び出されます。アクティビティが作成されると、アクティビティは作成済みの状態になります。 onCreate() メソッドでは、アクティビティのライフサイクル全体で 1 回だけ発生する基本的なアプリ起動ロジックを実行します。

たとえば、onCreate() の実装では、データをリストにバインドし、アクティビティを ViewModel に関連付けて、クラススコープ変数をいくつかインスタンス化しています。このメソッドはパラメータ savedInstanceState を受け取ります。これは、アクティビティの以前に保存された状態を含む Bundle オブジェクトです。アクティビティが以前に存在したことがない場合、Bundle オブジェクトの値は null になります。

アクティビティのライフサイクルに接続したライフサイクル対応コンポーネントがある場合、コンポーネントは ON_CREATE イベントを受け取ります。@OnLifecycleEvent アノテーションの付いたメソッドが呼び出されるため、ライフサイクル対応コンポーネントが、作成された状態に必要なセットアップ コードを実行できるようになります。

次の onCreate() メソッドの例は、ユーザー インターフェースの宣言(XML レイアウト ファイルで定義)、メンバー変数の定義、UI の一部の構成など、アクティビティの基本的な設定を示しています。この例では、XML レイアウト ファイルはファイルのリソース ID R.layout.main_activitysetContentView() に渡します。

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;

// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

XML ファイルを定義して setContentView() に渡す代わりに、アクティビティ コード内に新しい View オブジェクトを作成し、新しい View オブジェクトを ViewGroup に挿入してビュー階層を構築することもできます。このレイアウトを使用するには、ルート ViewGroupsetContentView() に渡します。ユーザー インターフェースの作成について詳しくは、ユーザー インターフェースのドキュメントをご覧ください。

アクティビティは作成済み状態のままになりません。onCreate() メソッドの実行が完了すると、アクティビティは開始状態になり、システムは onStart() メソッドと onResume() メソッドを連続して呼び出します。

onStart()

アクティビティが開始状態になると、システムによって onStart() が呼び出されます。この呼び出しにより、アプリがフォアグラウンドで操作可能になり、インタラクティブになる準備をしている間、アクティビティがユーザーに表示されます。たとえば、このメソッドでは UI を維持するコードが初期化されます。

アクティビティが開始状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントが ON_START イベントを受け取ります。

onStart() メソッドはすぐに完了し、作成状態と同様に、アクティビティは開始状態のままになりません。このコールバックが終了すると、アクティビティは再開状態になり、システムが onResume() メソッドを呼び出します。

onResume()

再開状態になったアクティビティはフォアグラウンドに移動し、システムによって onResume() コールバックが呼び出されます。これは、アプリがユーザーと対話する状態です。デバイスが電話を受けている、ユーザーが別のアクティビティに移動する、デバイス画面がオフになるなど、何かがアプリからフォーカスが離れるまで、この状態は維持されます。

アクティビティが再開状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは、ON_RESUME イベントを受け取ります。この時点で、ライフサイクル コンポーネントは、コンポーネントが表示されフォアグラウンドにある状態で実行する必要がある機能(カメラ プレビューの開始など)を有効にできます。

割り込みイベントが発生すると、アクティビティは一時停止状態になり、システムが onPause() コールバックを呼び出します。

アクティビティが一時停止状態から再開状態に戻ると、システムはもう一度 onResume() メソッドを呼び出します。このため、onResume() を実装して、onPause() 中に解放したコンポーネントを初期化し、アクティビティが再開状態になるたびに実行する必要があるその他の初期化を実行します。

以下に、コンポーネントが ON_RESUME イベントを受信したときにカメラにアクセスするライフサイクル対応コンポーネントの例を示します。

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

上記のコードは、LifecycleObserverON_RESUME イベントを受け取るとカメラを初期化します。ただし、マルチウィンドウ モードでは、一時停止状態であってもアクティビティが完全に表示される場合があります。たとえば、アプリがマルチウィンドウ モードで、ユーザーがアクティビティを含まないウィンドウをタップすると、アクティビティは一時停止状態に移行します。

アプリが再開したとき(表示されていてフォアグラウンドでアクティブになっているとき)にのみカメラをアクティブにする場合は、前述の ON_RESUME イベントの後にカメラを初期化します。アクティビティが一時停止されているが表示状態のとき(マルチウィンドウ モードなど)にカメラをアクティブのままにする場合は、ON_START イベントの後にカメラを初期化します。

ただし、アクティビティが一時停止されているときにカメラをアクティブにすると、マルチウィンドウ モードで、再開された別のアプリに対してカメラへのアクセスが拒否される場合があります。アクティビティが一時停止されている間、カメラをアクティブのままにする必要がある場合もありますが、そうすると、実際にはユーザー エクスペリエンス全体が低下する可能性があります。

このため、マルチウィンドウ モードのコンテキストでは、ライフサイクルのどこで共有システム リソースを制御するのが最も適しているかを慎重に検討してください。マルチウィンドウ モードのサポートについて詳しくは、マルチウィンドウのサポートをご覧ください。

初期化操作を実行するビルドアップ イベントの選択にかかわらず、対応するライフサイクル イベントを使用してリソースを解放してください。ON_START イベントの後に何かを初期化する場合は、ON_STOP イベントの後に解放または終了してください。ON_RESUME イベントの後に初期化する場合は、ON_PAUSE イベントの後に解放してください。

上記のコード スニペットでは、ライフサイクル対応コンポーネントにカメラの初期化コードを配置しています。代わりに、このコードを onStart()onStop() などのアクティビティのライフサイクル コールバックに直接配置することもできますが、この方法はおすすめしません。このロジックを独立したライフサイクル対応コンポーネントに追加すると、コードを複製することなく、複数のアクティビティでコンポーネントを再利用できます。ライフサイクル対応コンポーネントを作成する方法については、ライフサイクル対応コンポーネントによるライフサイクルの処理をご覧ください。

onPause()

このメソッドの呼び出しは、ユーザーがアクティビティを離れることを最初に示すときに行われますが、アクティビティが破棄されているとは限りません。アクティビティがフォアグラウンドではなくなったものの、ユーザーがマルチウィンドウ モードであれば、引き続き表示されることを示します。アクティビティがこの状態になる理由はいくつかあります。

  • onResume() コールバックに関するセクションで説明されているように、アプリの実行を中断するイベントが発生すると、現在のアクティビティが一時停止されます。これは最も一般的なケースです。
  • マルチウィンドウ モードでは、フォーカスを持つアプリは一度に 1 つのみになり、システムは他のすべてのアプリを一時停止します。
  • ダイアログなどの新しい半透明アクティビティを開くと、そのアクティビティがカバーしているアクティビティが一時停止します。アクティビティは部分的に表示されているものの、フォーカスされていない限り、一時停止は解除されません。

アクティビティが一時停止状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントが ON_PAUSE イベントを受け取ります。この時点でライフサイクル コンポーネントは、コンポーネントがフォアグラウンドにないときに実行する必要のない機能を停止できます(カメラ プレビューの停止など)。

onPause() メソッドを使用して、Activity が一時停止状態で、すぐに再開できると予想されるときに、続行できないオペレーションや適度に継続される可能性があるオペレーションを一時停止または調整します。

また、アクティビティが一時停止していてユーザーがそれを必要とせずに、システム リソース、センサー(GPS など)の処理、バッテリー駆動時間に影響を与えるリソースを解放するために、onPause() メソッドを使用することもできます。

ただし、onResume() についてのセクションで説明したように、アプリがマルチウィンドウ モードの場合、一時停止されたアクティビティは完全に表示されることがあります。マルチウィンドウ モードを適切にサポートするために、UI 関連のリソースとオペレーションを完全に解放または調整するには、onPause() ではなく onStop() を使用することを検討してください。

ON_PAUSE イベントに応答する次の LifecycleObserver の例は、前述の ON_RESUME イベントの例に対応するものです。ここでは、ON_RESUME イベントの受信後に初期化するカメラを解放します。

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

この例では、LifecycleObserverON_PAUSE イベントを受け取った後にカメラ リリースコードを配置しています。

onPause() の実行は非常に短く、必ずしも保存オペレーションを実行するのに十分な時間が用意されるわけではありません。このため、アプリやユーザーデータの保存、ネットワーク呼び出し、データベース トランザクションの実行に onPause() を使用しないでください。このような処理はメソッドが完了する前に完了しない可能性があります。

代わりに、onStop() の実行中に高負荷のシャットダウン オペレーションを実行します。onStop() で実行するのに適したオペレーションの詳細については、次のセクションをご覧ください。データの保存の詳細については、状態の保存と復元をご覧ください。

onPause() メソッドが完了しても、アクティビティが一時停止状態でなくなるわけではありません。アクティビティが再開されるか、ユーザーにまったく表示されないまで、アクティビティはこの状態のままになります。アクティビティが再開すると、システムは再び onResume() コールバックを呼び出します。

アクティビティが一時停止状態から再開状態に戻ると、Activity インスタンスはメモリに常駐したままで、onResume() が呼び出されたときにそのインスタンスがリコールされます。このシナリオでは、再開状態につながるコールバック メソッドの間に作成されたコンポーネントを再初期化する必要はありません。アクティビティが完全に非表示になった場合は、onStop() が呼び出されます。

onStop()

アクティビティがユーザーに表示されなくなると、停止状態になり、システムは onStop() コールバックを呼び出します。これは、新たに起動したアクティビティが画面全体を覆う場合に発生することがあります。また、アクティビティの実行が終了して終了が近づいているときにも、onStop() が呼び出されます。

アクティビティが停止状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントが ON_STOP イベントを受け取ります。この時点で、ライフサイクル コンポーネントは、コンポーネントが画面に表示されていない間に実行する必要のない機能を停止できます。

onStop() メソッドで、アプリがユーザーに表示されないときに不要なリソースを解放または調整します。たとえば、アプリがアニメーションを一時停止する場合や、高精度の位置情報アップデートから低精度の位置情報アップデートに切り替える場合です。onPause() ではなく onStop() を使用すると、ユーザーがマルチウィンドウ モードでアクティビティを表示している場合でも、UI 関連の処理が続行されます。

また、比較的 CPU 使用率の高いシャットダウン オペレーションを実行するには、onStop() を使用します。たとえば、データベースに情報を保存する最適なタイミングが見つからなかった場合は、onStop() で保存できます。下書きのメモの内容を永続ストレージに保存する onStop() の実装例を次に示します。

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

上記のコードサンプルでは、SQLite を直接使用しています。ただし、SQLite に抽象化レイヤを提供する永続ライブラリである Room を使用することをおすすめします。Room を使用するメリットと、アプリに Room を実装する方法について詳しくは、Room 永続ライブラリのガイドをご覧ください。

アクティビティが停止状態になると、Activity オブジェクトはメモリ内に常駐します。すべての状態とメンバー情報を保持しますが、ウィンドウ マネージャーにはアタッチされません。アクティビティが再開すると、この情報が再び呼び出されます。

再開状態につながるコールバック メソッドの間に作成されたコンポーネントを再初期化する必要はありません。また、レイアウト内の各 View オブジェクトの現在の状態も追跡されるため、ユーザーが EditText ウィジェットにテキストを入力すると、そのコンテンツは保持されるため、保存と復元の必要はありません。

注: アクティビティを停止すると、システムはメモリを回復する必要がある場合に、アクティビティを含むプロセスを破棄することがあります。 アクティビティが停止しているときにシステムがプロセスを破棄しても、システムは View オブジェクトの状態(EditText ウィジェットのテキストなど)を Bundle(Key-Value ペアの blob)に保持し、ユーザーがアクティビティに戻ると、それらを復元します。ユーザーが戻ったアクティビティを復元する方法について詳しくは、状態の保存と復元に関するセクションをご覧ください。

停止状態のアクティビティは、その後ユーザーとやり取りするために戻るか、実行が終了して消滅します。アクティビティが戻ると、システムは onRestart() を呼び出します。 Activity の実行が完了すると、システムは onDestroy() を呼び出します。

onDestroy()

onDestroy() はアクティビティが破棄される前に呼び出されます。システムは、次の 2 つの理由のいずれかでこのコールバックを呼び出します。

  1. ユーザーがアクティビティを完全に終了したか、アクティビティで finish() が呼び出されたため、アクティビティが終了した。
  2. デバイスの回転やマルチ ウィンドウ モードの開始など、構成の変更により、システムがアクティビティを一時的に破棄している。

アクティビティが破棄状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントはすべて ON_DESTROY イベントを受け取ります。ここで、ライフサイクル コンポーネントは、Activity が破棄される前に必要なものをすべてクリーンアップできます。

破棄される理由を判断するために Activity にロジックを入れるのではなく、ViewModel オブジェクトを使用して Activity の関連ビューデータを格納します。構成の変更により Activity が再作成された場合、ViewModel は保持され、次の Activity インスタンスに渡されるため、何もする必要はありません。

Activity が再作成されない場合、ViewModelonCleared() メソッドを呼び出します。これにより、破棄される前に必要なデータをクリーンアップできます。この 2 つのシナリオは、isFinishing() メソッドで区別できます。

アクティビティが終了した場合、onDestroy() は、アクティビティが受け取る最終ライフサイクル コールバックです。構成の変更の結果として onDestroy() が呼び出されると、システムはすぐに新しいアクティビティ インスタンスを作成し、新しい構成の新しいインスタンスに対して onCreate() を呼び出します。

onDestroy() コールバックは、以前のコールバックでは解放されなかったリソース(onStop() など)をすべて解放します。

アクティビティの状態とメモリからの退避

RAM を解放する必要がある場合、システムはプロセスを強制終了します。システムが特定のプロセスを強制終了する可能性は、そのときのプロセスの状態によって異なります。同様に、プロセスの状態は、プロセスで実行されているアクティビティの状態によって異なります。 表 1 に、プロセスの状態、アクティビティの状態、システムがプロセスを強制終了する可能性の相関関係を示します。この表は、プロセスが他のタイプのアプリケーション コンポーネントを実行していない場合にのみ適用されます。

強制終了の可能性 プロセスの状態 最終的なアクティビティの状態
最低 フォアグラウンド(フォーカスがあるか、間もなくフォーカスを取得する) 再開しました
可視化(フォーカスなし) 開始済み/一時停止済み
高画質 背景(非表示) 停止中
最高 なし 破棄

表 1. プロセスのライフサイクルとアクティビティの状態の関係

システムがメモリを解放するためにアクティビティを直接強制終了することはありません。代わりに、アクティビティが実行されているプロセスを強制終了し、アクティビティだけでなく、プロセス内で実行されている他のすべてのものも破棄します。システムによって開始されたプロセスが終了したときにアクティビティの UI の状態を保持および復元する方法については、状態の保存と復元のセクションをご覧ください。

ユーザーは、[設定] にあるアプリケーション マネージャーを使用して、対応するアプリを強制終了することもできます。

プロセスの詳細については、プロセスとスレッドの概要をご覧ください。

一時的な UI の状態の保存と復元

ユーザーは、構成変更(回転やマルチウィンドウ モードへの切り替えなど)全般にわたってアクティビティの UI の状態が変わらないことを想定します。ただし、このような構成の変更が発生すると、デフォルトではアクティビティはシステムにより破棄され、アクティビティ インスタンスに保存されている UI の状態はすべてワイプされます。

同様に、ユーザーは一時的に別のアプリに切り替えてから元のアプリに戻った場合にも、UI の状態が同じままであることを想定します。しかし、ユーザーが離れてアクティビティが停止している間、アプリのプロセスがシステムによって破棄されることがあります。

システム制約によってアクティビティが破棄された場合は、ViewModel onSaveInstanceState()、またはローカル ストレージを組み合わせて使用し、ユーザーの一時的な UI の状態を保持します。システムの動作と比較した場合のユーザーの想定の詳細と、システムによって開始されるアクティビティとプロセスの終了全体で複雑な UI の状態データを適切に保持する方法については、 UI の状態を保存するをご覧ください。

このセクションでは、インスタンスの状態の概要と、アクティビティ自体のコールバックである onSaveInstance() メソッドの実装方法について説明します。UI データが軽量である場合は、onSaveInstance() のみを使用して、構成の変更とシステムが開始したプロセスの終了の両方で UI の状態を保持できます。ただし、onSaveInstance() にはシリアル化とシリアル化解除のコストがかかるため、ほとんどの場合、 UI の状態を保存するで説明されているように ViewModelonSaveInstance() の両方を使用します。

注: 構成の変更、必要に応じてアクティビティの再作成を制限する方法、View システムと Jetpack Compose からの構成変更に対応する方法については、構成の変更を処理するのページをご覧ください。

インスタンスの状態

ユーザーが [戻る] ボタンを押した場合や、アクティビティが finish() メソッドを呼び出して破棄を通知した場合など、アプリの通常の動作によってアクティビティが破棄されるシナリオはいくつかあります。

ユーザーが [戻る] を押すか、アクティビティ自体が終了したためにアクティビティが破棄されると、その Activity インスタンスに対するシステムとユーザーの概念はどちらも永久に失われます。これらのシナリオでは、ユーザーの期待とシステムの動作が一致しているため、デベロッパーは追加の作業を必要としません。

ただし、システムの制約(構成の変更やメモリ負荷など)によってアクティビティが破棄された場合、実際の Activity インスタンスは失われても、システムはその存在を記憶します。ユーザーがアクティビティに戻ろうとすると、システムはアクティビティが破棄された時点でのアクティビティの状態を記述する一連の保存済みデータを使用して、アクティビティの新しいインスタンスを作成します。

システムが以前の状態を復元するために使用する保存済みデータを、インスタンスの状態といいます。これは、Bundle オブジェクトに格納されている Key-Value ペアのコレクションです。デフォルトでは、システムは Bundle インスタンスの状態を使用して、アクティビティ レイアウト内の各 View オブジェクトに関する情報(EditText ウィジェットに入力されたテキスト値など)を保存します。

そのため、アクティビティのインスタンスが破棄されてから再作成された場合、レイアウトの状態はコードを必要とすることなく以前の状態に復元されます。ただし、アクティビティには、アクティビティ内のユーザーの作業状況を追跡するメンバー変数など、復元する必要があると考えられる状態に関する情報が他にも含まれている場合があります。

注: Android システムがアクティビティ内のビューの状態を復元するためには、android:id 属性で各ビューに一意の ID を割り当てる必要があります。

Bundle オブジェクトはメインスレッドでのシリアル化を必要とし、システム プロセスのメモリを消費するため、少量以上のデータを保持するには適していません。ごく少量のデータを保持するには、 UI の状態を保存するで説明されているように、永続ローカル ストレージ、onSaveInstanceState() メソッド、ViewModel クラスを使用してデータを保持します。

onSaveInstanceState() を使用してシンプルで軽量な UI の状態を保存する

アクティビティの停止が開始されると、システムは onSaveInstanceState() メソッドを呼び出します。これにより、アクティビティは状態情報をインスタンス状態バンドルに保存できます。このメソッドのデフォルトの実装では、EditText ウィジェット内のテキストや ListView ウィジェットのスクロール位置など、アクティビティのビュー階層の状態に関する一時的な情報が保存されます。

アクティビティの追加インスタンス状態情報を保存するには、onSaveInstanceState() をオーバーライドして、アクティビティが予期せず破棄された場合に保存される Bundle オブジェクトに Key-Value ペアを追加します。onSaveInstanceState() をオーバーライドする際、デフォルトの実装でビュー階層の状態を保存するには、スーパークラスの実装を呼び出す必要があります。次の例はこの処理を示しています。

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

注: onSaveInstanceState() は、ユーザーがアクティビティを明示的に閉じたときや、finish() が呼び出されたときは呼び出されません。

ユーザー設定やデータベース データなどの永続データを保存するには、アクティビティがフォアグラウンドになっているときに適切な方法を採用してください。このような機会がない場合は、onStop() メソッド中に永続データを保存してください。

保存済みのインスタンスの状態を使用してアクティビティの UI の状態を復元する

以前に破棄したアクティビティを再作成する場合は、システムがアクティビティに渡した Bundle から保存済みのインスタンスの状態を復元できます。onCreate() コールバック メソッドと onRestoreInstanceState() コールバック メソッドは、インスタンスの状態情報を含む同じ Bundle を受け取ります。

onCreate() メソッドは、システムがアクティビティの新しいインスタンスを作成しているか、以前のインスタンスを再作成しているかにかかわらず呼び出されるため、読み取りを行う前に、状態 Bundle が null かどうかを確認する必要があります。null の場合は、破棄された以前のインスタンスが復元されるのではなく、アクティビティの新しいインスタンスが作成されます。

次のコード スニペットは、onCreate() で一部の状態データを復元する方法を示しています。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

onCreate() 中に状態を復元する代わりに、onRestoreInstanceState() を実装することもできます。これはシステムが onStart() メソッドの後で呼び出します。復元する保存済み状態がある場合にのみ、onRestoreInstanceState() が呼び出されるため、Bundle が null であるかどうかをチェックする必要はありません。

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

注意: デフォルトの実装でビュー階層の状態を復元できるように、常に onRestoreInstanceState() のスーパークラス実装を呼び出してください。

アクティビティ間の移動

ユーザーがデバイスの [戻る] ボタンをタップした場合や、アクティビティが別のアクティビティを起動した場合などは、アプリの存続期間中に、アプリでアクティビティの開始と終了が何度も行われる可能性があります。

このセクションでは、アクティビティの適切な遷移を実装するうえで理解しておく必要があるトピックについて説明します。 これらのトピックには、別のアクティビティからのアクティビティの開始、アクティビティの状態の保存、アクティビティの状態の復元などがあります。

アクティビティを別のアクティビティから開始する

特定の時点で、アクティビティが別のアクティビティを開始する必要が生じることは頻繁にあります。たとえば、アプリが現在の画面から新しい画面に移動する必要がある場合に、この動作が必要です。

開始しようとしている新しいアクティビティの結果を返すかどうかに応じて、startActivity() メソッドまたは startActivityForResult() メソッドを使用して新しいアクティビティを開始します。いずれの場合も、Intent オブジェクトを渡します。

Intent オブジェクトでは、開始するアクティビティを正確に指定するか、実行するアクションの種類を記述します。システムは適切なアクティビティを選択します。別のアプリのアクティビティを選択することもできます。Intent オブジェクトには、開始したアクティビティで使用する少量のデータを含めることもできます。 Intent クラスの詳細については、インテントとインテント フィルタをご覧ください。

startActivity()

新しく開始されたアクティビティが結果を返す必要がなければ、現在のアクティビティは startActivity() メソッドを呼び出してアクティビティを開始できます。

独自のアプリで作業する場合には、既知のアクティビティを起動するだけで済むことが頻繁にあります。 たとえば、次のコード スニペットは、SignInActivity という名前のアクティビティを起動する方法を示しています。

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

アクティビティから取得したデータを使用して、アプリでメールやテキスト メッセージの送信、ステータスの更新などの操作を行うことが必要な場合もあります。 この場合、アプリはそのようなアクションを実行する独自のアクティビティを持っていない場合があるため、代わりに、デバイス上の他のアプリが提供するアクティビティを利用して、自動的にアクションを実行できます。

ここでインテントが重要になります。実行するアクションを記述するインテントを作成すると、システムが別のアプリから適切なアクティビティを起動します。インテントを処理できるアクティビティが複数ある場合は、ユーザーが使用するアクティビティを選択できます。たとえば、ユーザーがメール メッセージを送信できるようにするには、次のインテントを作成します。

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

インテントに追加された EXTRA_EMAIL エクストラは、送信先のメールアドレスの文字列配列です。メールアプリがこのインテントに応答すると、エクストラで指定された文字列配列が読み取られ、メール作成フォームの「to」フィールドにアドレスが配置されます。その場合、メールアプリのアクティビティが開始され、ユーザーが終了すると、アクティビティが再開されます。

startActivityForResult()

アクティビティの終了時に、アクティビティから結果を戻すことが必要な場合があります。たとえば、ユーザーが連絡先リストから人を選択できるようにするアクティビティを開始できます。終了すると、選択された人物が返されます。これを行うには、startActivityForResult(Intent, int) メソッドを呼び出します。ここで、整数パラメータで呼び出しを識別します。

この識別子は、同じアクティビティからの startActivityForResult(Intent, int) への複数の呼び出しを区別するためのものです。これはグローバル識別子ではなく、他のアプリやアクティビティと競合するリスクはありません。結果は onActivityResult(int, int, Intent) メソッドを介して返されます。

子アクティビティが終了すると、setResult(int) を呼び出して親にデータを返すようにすることができます。 子アクティビティは結果コードを指定する必要があります。結果コードには、標準の結果である RESULT_CANCELEDRESULT_OK、または RESULT_FIRST_USER で始まる任意のカスタム値を指定できます。

また、子アクティビティは、必要に応じて追加データを含む Intent オブジェクトを返すことができます。親アクティビティは、onActivityResult(int, int, Intent) メソッドと、親アクティビティが最初に指定した整数 ID を使用して、情報を受け取ります。

子アクティビティがなんらかの理由(クラッシュなど)で失敗した場合、親アクティビティはコード RESULT_CANCELED を含む結果を受け取ります。

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
     // ...

     static final int PICK_CONTACT_REQUEST = 0;

     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             // When the user center presses, let them pick a contact.
             startActivityForResult(
                 new Intent(Intent.ACTION_PICK,
                 new Uri("content://contacts")),
                 PICK_CONTACT_REQUEST);
            return true;
         }
         return false;
     }

     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
         if (requestCode == PICK_CONTACT_REQUEST) {
             if (resultCode == RESULT_OK) {
                 // A contact was picked. Display it to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

アクティビティを連携させる

あるアクティビティが別のアクティビティを開始すると、双方でライフサイクルの遷移が発生します。 最初のアクティビティが動作を停止して、一時停止状態または停止状態となり、もう 1 つのアクティビティが作成されます。これらのアクティビティでディスクなどに保存されているデータを共有している場合は、2 つ目のアクティビティが作成される前に最初のアクティビティが完全に停止することはない点を理解しておくことが重要です。むしろ、2 つ目のアクティビティを開始するプロセスは、最初のアクティビティを停止するプロセスと重複します。

ライフサイクル コールバックの順序は、特に 2 つのアクティビティが同じプロセス(つまり同じアプリ)にあり、一方が他方のアクティビティを開始している場合に、明確に定義されます。アクティビティ A がアクティビティ B を開始する場合の動作の順序は次のとおりです。

  1. アクティビティ A の onPause() メソッドが実行されます。
  2. アクティビティ B の onCreate()onStart()onResume() の各メソッドが順次実行されます これで、アクティビティ B にユーザー フォーカスが加わりました。
  3. アクティビティ A が画面に表示されなくなると、その onStop() メソッドが実行されます。

ライフサイクル コールバックのこのシーケンスにより、あるアクティビティから別のアクティビティへの情報の遷移を管理できます。