アクティビティのライフサイクルのステージ

概要

この Codelab では、Android の基本的な要素であるアクティビティについて詳しく学習します。アクティビティのライフサイクルとは、アクティビティが全期間にわたってとりうる一連の状態です。ライフサイクルは、アクティビティが最初に作成されたときから、アクティビティが破棄され、そのリソースがシステムによって回収されるときまで続きます。ユーザーがアプリのアクティビティ間を移動したり、アプリの内外に移動したりすると、各アクティビティはライフサイクルのさまざまな状態に遷移します。

Android デベロッパーは、アクティビティのライフサイクルを理解しておく必要があります。アクティビティがライフサイクルの状態の変化に正しく反応しない場合、アプリに予期せぬバグが発生してユーザーの混乱を招いたり、Android システムのリソースを過度に使用したりする可能性があります。Android のライフサイクルを理解し、ライフサイクルの状態の変化に適切に対応することは、Android デベロッパーにとって不可欠です。

前提となる知識

  • アクティビティの概要と、アプリでアクティビティを作成する方法。
  • アクティビティの onCreate() メソッドの概要と、このメソッドで実行されるオペレーションの種類。

学習内容

  • ロギング情報を Logcat に出力する方法。
  • Activity のライフサイクルの基本と、アクティビティが状態間を遷移したときに呼び出されるコールバック。
  • ライフサイクル コールバック メソッドをオーバーライドして、アクティビティのライフサイクルのさまざまなタイミングでオペレーションを実行する方法。

演習内容

  • DessertClicker という演習用アプリを変更して、Logcat に表示されるロギング情報を追加します。
  • ライフサイクル コールバック メソッドをオーバーライドして、アクティビティの状態の変化を記録します。
  • アプリを実行して、アクティビティが開始、停止、再開されたときに表示されるロギング情報を確認します。
  • デバイスの構成変更によって失われる可能性のあるアプリデータを保持する onSaveInstanceState() メソッドを実装します。アプリを再起動したときにそのデータを復元するコードを追加します。

この Codelab では、DessertClicker というスターター アプリを使用します。このアプリでは、ユーザーが画面上のデザートをタップするたびに、そのデザートが「購入」されます。アプリは、購入されたデザートの数とユーザーが支出した合計額のレイアウトの値を更新します。

8216c20f5571fc04.png

このアプリには、Android のライフサイクルに関連するバグがいくつか含まれています。たとえば、特定の状況でデザートの値が 0 にリセットされます。Android のライフサイクルを理解することで、このような問題が発生する理由とその解決方法がわかるようになります。

演習用アプリを入手する

DessertClicker 初期コードをダウンロードして、Android Studio で開きます。

GitHub の初期コードを使用する場合、フォルダ名は android-basics-kotlin-dessert-clicker-app-starter です。Android Studio でプロジェクトを開くときは、このフォルダを選択します。

この Codelab のコードを取得して Android Studio で開くには、以下の手順に沿って操作します。

コードを取得する

  1. 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
  2. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ダイアログが表示されます。

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. ダイアログで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ってください。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで [Open an existing Android Studio project] をクリックします。

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

注: Android Studio がすでに開いている場合は、メニューから [File] > [New] > [Import Project] を選択します。

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. [Import Project] ダイアログで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開かれるまで待ちます。
  4. 実行ボタン j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg をクリックして、アプリをビルドし、実行します。期待どおりに動作することを確認します。
  5. [Project] ツール ウィンドウでプロジェクト ファイルを見て、アプリがどのように実装されているかを確認します。

すべてのアクティビティには、ライフサイクルがあります。これは植物や動物のライフサイクルになぞらえたもので、たとえば次に示す蝶のライフサイクルでは、生まれてから成虫になり、死に至るまでの過程で、さまざまな状態を遷移します。

c685f48ff799f0c9.png

アクティビティのライフサイクルも同様に、最初に初期化されてから、最終的に破棄され、システムによってメモリが回収されるまでの過程で、アクティビティが遷移するさまざまな状態によって構成されています。ユーザーがアプリの起動、アクティビティ間の移動、アプリ内外への移動を行うと、アクティビティの状態が変わります。次の図は、アクティビティのライフサイクルの状態をすべて示しています。これらの状態は、名前が示すようにそれぞれアクティビティのステータスを表します。

c803811f4cb4034b.png

多くの場合、アクティビティのライフサイクルの状態が変わったタイミングで、動作の変更やコードの実行が必要になります。したがって、Activity クラス自体と、Activity のサブクラス(AppCompatActivity など)は、一連のライフサイクル コールバック メソッドを実装しています。Android は、アクティビティがある状態から別の状態に遷移したときに、これらのコールバックを呼び出します。そのため、ライフサイクルの状態の変化に応じて、自分のアクティビティ内でこれらのメソッドをオーバーライドしてタスクを実行できます。次の図は、ライフサイクルの状態と、オーバーライド可能なコールバックを示しています。

f6b25a71cec4e401.png

コールバックが呼び出されるタイミングと、各コールバック メソッドで実行される処理を理解しておくことが重要です。ただし、上記の図はどちらも複雑で、混乱を招く可能性があります。この Codelab では、それぞれの状態とコールバックの概要を読むだけでなく、その仕組みについても理解するようにしてください。

ステップ 1: onCreate() メソッドを確認してロギングを追加する

Android のライフサイクルで何が起きているのかを調べる際に、さまざまなライフサイクル メソッドが呼び出されるタイミングを把握しておくと役に立ちます。これにより、DessertClicker のどの部分に問題があるのかを追跡できます。

これを簡単に行うには、Android のロギング機能を使用します。ロギングを使用すると、アプリの実行中にコンソールに短いメッセージを書き込むことができます。これを使用して、各コールバックがトリガーされたときにメッセージを表示できます。

  1. DessertClicker アプリを実行し、デザートの写真を複数回タップします。[Desserts Sold] の値と合計金額がどのように変化するかに注目してください。
  2. MainActivity.kt を開き、このアクティビティの onCreate() メソッドを確認します。
override fun onCreate(savedInstanceState: Bundle?) {
...
}

アクティビティのライフサイクルの図にある onCreate() メソッドに見覚えがあるかもしれません。以前にこのコールバックを使用したことがあるはずです。このメソッドはすべてのアクティビティに実装する必要があります。onCreate() メソッドでは、アクティビティに対して 1 回限りの初期化を行います。たとえば、レイアウトのインフレート、クリック リスナーの定義、ビュー バインディングの設定などを onCreate() で行うことができます。

9be2255ff49e0af8.png

onCreate() ライフサイクル メソッドは、アクティビティの初期化直後(新しい Activity オブジェクトがメモリに作成されたとき)に 1 回呼び出されます。onCreate() が実行されると、アクティビティは作成されたと見なされます。

  1. onCreate() メソッドで、super.onCreate() 呼び出しの直後に次の行を追加します。
Log.d("MainActivity", "onCreate Called")
  1. 必要に応じて Log クラスをインポートします(Alt+Enter(Mac の場合は Option+Enter)を押して、[Import] を選択します)。自動インポートを有効にしている場合、インポートは自動的に行われます。
import android.util.Log

Log クラスは、メッセージを Logcat に書き込みます。Logcat は、メッセージを記録するためのコンソールです。Android からのアプリに関するメッセージ(Log.d() メソッドや他の Log クラスメソッドを使用してログに明示的に送信したメッセージを含む)がここに表示されます。

このコマンドは次の 3 つの部分で構成されています。

  • ログメッセージの優先度(メッセージの重要度)。この例では、Log.d() メソッドはデバッグ メッセージを書き込みます。Log クラスのその他のメソッドには、情報メッセージ用の Log.i()、エラー メッセージ用の Log.e()、警告メッセージ用の Log.w()、または詳細メッセージ用の Log.v() が含まれます。
  • ログタグ(最初のパラメータ)。この例では "MainActivity"。このタグは、Logcat でログメッセージを簡単に見つけるための文字列です。通常、このタグはクラスの名前です。
  • 実際のログメッセージ(2 番目のパラメータ)。短い文字列であり、この例では "onCreate called"

コンパイル時の定数は、変更されない値です。変数宣言の前に const を使用して、コンパイル時の定数としてマークします。

  1. DessertClicker アプリをコンパイルして実行します。デザートをタップしても、アプリの動作には特に違いは生じません。Android Studio の画面の下部で [Logcat] タブをクリックします。

ff9c50376701877f.png

  1. [Logcat] ウィンドウで、検索フィールドに「D/MainActivity」と入力します。

bb0b78600cd47789.png

Logcat には多数のメッセージが含まれている場合がありますが、ほとんどは役に立ちません。Logcat エントリはさまざまな方法でフィルタできますが、検索するのが最も簡単です。コード内で MainActivity をログタグとして使用したため、そのタグを使用してログをフィルタできます。最初に D/ を追加すると、Log.d() によって作成されたデバッグ メッセージが表示されます。

ログメッセージには、日時、パッケージの名前(com.example.android.dessertclicker)、ログタグ(先頭が D/)、実際のメッセージが含まれます。このメッセージがログに表示されていることから、onCreate() が実行されたことがわかります。

ステップ 2: onStart() メソッドを実装する

onStart() ライフサイクル メソッドは、onCreate() の直後に呼び出されます。onStart() が実行されると、アクティビティが画面に表示されます。アクティビティを初期化するときに 1 回だけ呼び出される onCreate() とは異なり、onStart() はアクティビティのライフサイクルで何度も呼び出すことができます。

385df4ce82ae2de9.png

onStart() は、対応する onStop() ライフサイクル メソッドとペアになることに注意してください。ユーザーがアプリを起動してからデバイスのホーム画面に戻ると、アクティビティは停止し、画面に表示されなくなります。

  1. Android Studio で、MainActivity.kt を開き、MainActivity クラス内にカーソルを置いて、[Code] > [Override Methods] を選択するか、Control+o(Mac の場合は Command+o)を押します。ダイアログが開き、このクラスでオーバーライドできるすべてのメソッドのリストが表示されます。e1f2460242b2ae.png
  2. onStart を入力すると、適切なメソッドを検索できます。次の一致項目までスクロールするには、下矢印を使用します。リストから [onStart()] を選択し、[OK] をクリックしてボイラープレート オーバーライド コードを挿入します。コードは次のようになります。
override fun onStart() {
   super.onStart()
}
  1. 次の定数を、MainActivity.kt のトップレベル(クラス宣言 class MainActivity. の上)に追加します。
const val TAG = "MainActivity"
  1. onStart() メソッド内に、ログメッセージを追加します。
override fun onStart() {
   super.onStart()
   Log.d(TAG, "onStart Called")
}
  1. DessertClicker アプリをコンパイルして実行し、[Logcat] ペインを開きます。検索フィールドに「D/MainActivity」と入力して、ログをフィルタします。onCreate() メソッドと onStart() メソッドの両方が相次いで呼び出され、アクティビティが画面に表示されます。
  2. デバイスのホームボタンを押してから、[最近] 画面を使用してアクティビティに戻ります。アクティビティは、前回中断したところからすべての値が同じまま再開され、2 回目の onStart() が Logcat に記録されます。また、onCreate() メソッドは通常、再度呼び出されることはありません。
16:19:59.125 31107-31107/com.example.android.dessertclicker D/MainActivity: onCreate Called
16:19:59.372 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called
16:20:11.319 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called

ステップ 3: ログ ステートメントを追加する

このステップでは、他のすべてのライフサイクル メソッドのロギングを実装します。

  1. MainActivity の残りのライフサイクル メソッドをオーバーライドして、それぞれのログ ステートメントを追加します。以下にコードを示します。
override fun onResume() {
   super.onResume()
   Log.d(TAG, "onResume Called")
}

override fun onPause() {
   super.onPause()
   Log.d(TAG, "onPause Called")
}

override fun onStop() {
   super.onStop()
   Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
   super.onDestroy()
   Log.d(TAG, "onDestroy Called")
}

override fun onRestart() {
   super.onRestart()
   Log.d(TAG, "onRestart Called")
}
  1. DessertClicker を再度コンパイルして実行し、Logcat を確認します。今回は onCreate()onStart() に加えて、onResume() ライフサイクル コールバックのログメッセージが表示されています。
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

アクティビティがゼロから開始されると、次の 3 つのライフサイクル コールバックが順に呼び出されます。

  • onCreate(): アプリを作成します。
  • onStart(): アプリを起動して画面に表示します。
  • onResume(): アクティビティにフォーカスを合わせ、ユーザーが操作できるように準備します。

onResume() メソッドは、その名前とは異なり、再開する対象がない場合でも起動時に呼び出されます。

160054d59f67519.png

これで、DessertClicker アプリのロギングのセットアップが完了しました。次に、さまざまな方法でアプリを使用し、それに応じてライフサイクル コールバックがどのようにトリガーされるかを確認しましょう。

ユースケース 1: アクティビティの開始と終了

まず、アプリを初めて起動し、その後完全に終了するという、基本的なユースケースから始めます。

  1. DessertClicker アプリをまだ実行していない場合は、コンパイルして実行します。すでに説明したとおり、アクティビティの初回起動時に onCreate()onStart()onResume() のコールバックが呼び出されます。
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called
  1. カップケーキを数回タップします。
  2. デバイスの [戻る] ボタンをタップします。onPause()onStop()onDestroy() の順序で呼び出されていることが、Logcat で確認できます。
2020-10-16 10:31:53.850 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:31:54.620 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called

この場合、[戻る] ボタンを使用することで、アクティビティ(とアプリ)が完全に終了しています。onDestroy() メソッドが実行されると、アクティビティが完全にシャットダウンされ、ガベージ コレクションの対象になる可能性があります。ガベージ コレクションとは、使用しなくなったオブジェクトの自動クリーンアップを指します。onDestroy() が呼び出されると、システムはこれらのリソースを破棄できることを認識し、メモリのクリーンアップを開始します。2dcc4d9c6478a9f4.png コードがアクティビティの finish() メソッドを呼び出すか、ユーザーがアプリを強制終了した場合(たとえば、[最近] 画面でアプリを強制終了または終了できます)も、アクティビティが完全にシャットダウンされる可能性があります。また、アプリが長時間画面に表示されていない場合、Android システムが独自にアクティビティをシャットダウンすることもあります。これは、バッテリーを節約するためと、アプリのリソースを他のアプリで使用できるようにするために行われます。

  1. 概要画面で開いているアプリをすべて見つけて、DessertClicker アプリに戻ります(概要画面は、[最近] 画面または [最近使ったアプリ] とも呼ばれます)。Logcat は次のようになります。
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 10:38:00.733 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:38:00.787 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:38:00.788 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

アクティビティは前のステップで破棄されたため、アプリに戻ると、Android は新しいアクティビティを開始し、onCreate()onStart()onResume() メソッドを呼び出します。前のアクティビティの DessertClicker ログは保持されていないことに注意してください。

onCreate() メソッドは重要なステップです。ここで最初の初期化を行い、最初のレイアウトをインフレートして設定し、変数を初期化します。

ユースケース 2: アクティビティ間の移動

アプリを起動して完全に終了したことで、アクティビティが初めて作成されたときのライフサイクルの状態の大部分を見ることができました。また、アクティビティが完全にシャットダウンされて破棄されたときのライフサイクルの状態もすべて確認しました。しかし、ユーザーは Android デバイスを操作する際に、アプリを切り替えたり、ホームに戻ったり、新しいアプリを起動したり、電話などの他のアクティビティによる中断を処理したりします。

その場合、ユーザーがアクティビティから離れるたびにそのアクティビティが完全に終了するわけではありません。

  • アクティビティが画面に表示されなくなると、そのアクティビティはバックグラウンドに移行します(これとは逆の状態が、アクティビティがフォアグラウンドにあるとき、または画面に表示されているときです)。
  • ユーザーがアプリに戻ると、同じアクティビティが再開され、再び表示されます。ライフサイクルのこの部分は、アプリの表示可能なライフサイクルと呼ばれます。

一般に、アプリがバックグラウンドにあるときには、システム リソースとバッテリーの消耗を抑えるために、アプリがアクティブに実行されないようにする必要があります。Activity ライフサイクルとそのコールバックを使用して、アプリがバックグラウンドに移行するタイミングを把握することで、実行中のオペレーションを一時停止できます。その後、アプリがフォアグラウンドになったらオペレーションを再開します。

このステップでは、アプリがバックグラウンドに移行してフォアグラウンドに戻ったときのアクティビティのライフサイクルを確認します。

  1. DessertClicker アプリを実行した状態で、カップケーキを数回クリックします。
  2. デバイスのホームボタンを押して、Android Studio の Logcat を確認します。ホーム画面に戻ってもアプリは完全にシャットダウンされることはなく、バックグラウンドに移行されます。onPause() メソッドと onStop() メソッドは呼び出されますが、onDestroy() は呼び出されません。
2020-10-16 10:41:05.383 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:41:05.966 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called

onPause() が呼び出されると、アプリのフォーカスが失われます。onStop() の後、アプリは画面に表示されなくなります。アクティビティは停止されていますが、Activity オブジェクトはメモリ内(バックグラウンド)に残っています。アクティビティは破棄されていません。ユーザーがアプリに戻ってくる可能性があるため、Android はアクティビティ リソースを保持しています。b488b32801220b79.png

  1. [最近] 画面を使用してアプリに戻ります。Logcat を確認すると、アクティビティが onRestart()onStart() で再起動され、その後 onResume() で再開されています。
2020-10-16 10:42:18.144 22064-22064/com.example.android.dessertclicker D/MainActivity: onRestart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

アクティビティがフォアグラウンドに戻ったときに、onCreate() メソッドが再び呼び出されることはありません。アクティビティ オブジェクトは破棄されていないため再作成の必要はなく、onCreate() の代わりに onRestart() メソッドが呼び出されます。アクティビティがフォアグラウンドに戻ったときに、[Desserts Sold] の数値が保持されていることに注意してください。

  1. デバイスの [最近] 画面に複数のアプリが表示されるように、DessertClicker 以外のアプリを少なくとも 1 つ起動します。
  2. [最近] 画面を開き、別の最近のアクティビティを開きます。最近使ったアプリに戻って、DessertClicker をフォアグラウンドに戻します。

ホームボタンを押したときと同じコールバックが Logcat に表示されていることに注意してください。アプリがバックグラウンドになるときに onPause()onStop() が呼び出され、フォアグラウンドに戻るときに onRestart()onStart()onResume() が呼び出されます。

これらのメソッドは、アプリが停止されてバックグラウンドに移行するとき、またはアプリがフォアグラウンドに戻って再開されるときに呼び出されます。この間にアプリでなんらかの処理を行う必要がある場合は、関連するライフサイクル コールバック メソッドをオーバーライドします。

onRestart() についてはどうでしょうか。onRestart() メソッドは onCreate() に似ています。アクティビティが表示される前に、onCreate() または onRestart() が呼び出されます。初回のみ onCreate() メソッドが呼び出され、それ以降は onRestart() が呼び出されます。そのため、onRestart() メソッドには、アクティビティを起動するのが初めてではない場合にのみ呼び出すコードを配置します。

ユースケース 3: アクティビティの一部を非表示にする

アプリが起動されて onStart() が呼び出されると、アプリが画面に表示されることを説明しました。アプリが再開されて onResume() が呼び出されると、アプリがユーザー フォーカスを取得し、ユーザーがアプリを操作できるようになります。アプリが完全に画面に表示され、ユーザー フォーカスを持つライフサイクルは、操作可能なライフサイクルと呼ばれます。

アプリがバックグラウンドに移行すると、onPause() の後にフォーカスが失われ、onStop() 以降はアプリが表示されなくなります。

アクティビティにユーザー フォーカスがない状態でも、画面上に部分的に表示することはできるため、フォーカスと表示の違いは重要です。このステップでは、アクティビティが部分的に表示されているものの、ユーザー フォーカスがないケースを見ていきます。

  1. DessertClicker アプリが実行されている状態で、画面右上の [Share] ボタンをクリックします。
  2. 共有アクティビティが画面の下半分に表示されますが、アクティビティは引き続き画面の上半分に表示されています。e2319779260eb5ee.png

9ddc8b1dc79b1bff.png

  1. Logcat を確認すると、onPause() のみが呼び出されています。
2020-10-16 11:00:53.857 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called

このユースケースでは、アクティビティがまだ部分的に表示されているため、onStop() は呼び出されません。ただし、ユーザー フォーカスはこのアクティビティではなく、フォアグラウンドの「共有」アクティビティにあるため、ユーザーはこのアクティビティを操作できません。

ここで、フォーカスと表示の違いが重要な理由を考えてみましょう。onPause() のみによる中断は通常、そのアクティビティに戻るか、別のアクティビティまたはアプリに移動するまでの短い時間しか持続しません。一般に、UI を継続的に更新して、アプリの残りの部分がフリーズしたように見えないようにする必要があります。

onPause() で実行されるコードにより、他のアクティビティやアプリの表示が妨げられるため、onPause() のコードは軽量化します。たとえば、電話の着信があった場合に、onPause() のコードによって着信通知が遅れる可能性があります。

  1. 共有ダイアログの外側をクリックするとアプリに戻り、onResume() が呼び出されます。

onResume()onPause() はどちらもフォーカスに関係があります。onResume() メソッドはアクティビティにフォーカスがあるときに呼び出され、onPause() はアクティビティがフォーカスを失ったときに呼び出されます。

また、アクティビティのライフサイクルを管理するうえで、構成の変更がアクティビティのライフサイクルにどのように影響するかを理解しておくことも重要です。

構成の変更は、デバイスの状態が大きく変更されたために、システムにとってアクティビティを完全にシャットダウンして再構築するのが最も手間がかからない場合に行われます。たとえば、ユーザーがデバイスの言語を変更した場合、異なるテキスト方向と文字列の長さに対応するためにレイアウト全体の変更が必要になる可能性あります。ユーザーがデバイスをホルダーに装着した場合や物理キーボードを追加した場合は、アプリのレイアウトで別のディスプレイ サイズやレイアウトを利用する必要があるかもしれません。また、デバイスの向きが変わった場合(デバイスが縦向きから横向き、またはその逆に回転した場合)は、新しい向きに合わせてレイアウトの変更が必要になることがあります。次のシナリオで、アプリがどのように動作するか見てみましょう。

デバイスの回転時のデータ損失

  1. アプリをコンパイルして実行し、Logcat を開きます。
  2. デバイスまたはエミュレータを回転させて横向きにします。エミュレータを左右に回転させるには、回転ボタン、または Control キーと矢印キー(Mac では Command キーと矢印キー)を使用します。623fce7c623d42bd.png
  3. Logcat で出力を調べます。MainActivity の出力をフィルタします。
2020-10-16 11:03:09.618 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:09.806 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 11:03:09.808 23206-23206/com.example.android.dessertclicker D/MainActivity: onResume Called
2020-10-16 11:03:24.488 23206-23206/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:03:24.490 23206-23206/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:03:24.493 23206-23206/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 11:03:24.520 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:24.569 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called

デバイスまたはエミュレータの画面を回転させると、すべてのライフサイクル コールバックが呼び出され、アクティビティがシャットダウンされます。その後アクティビティが再作成されると、すべてのライフサイクル コールバックが呼び出され、アクティビティが開始されます。

  1. デバイスが回転し、アクティビティがシャットダウンおよび再作成されると、アクティビティはデフォルト値で開始されます。つまり、デザートの販売数と収益がゼロにリセットされます。

onSaveInstanceState() を使用してバンドルデータを保存する

onSaveInstanceState() メソッドは、Activity が破棄された場合に必要なデータを保存するためのコールバックです。ライフサイクル コールバックの図では、アクティビティが停止した後に onSaveInstanceState() が呼び出されています。このメソッドは、アプリがバックグラウンドに移行するたびに呼び出されます。

c259ab6beca0ca88.png

onSaveInstanceState() 呼び出しは、アクティビティがフォアグラウンドを終了したときに、少量の情報をバンドルに保存するための安全策として考えることができます。アプリがシャットダウンされるまで待機するとリソース不足になる可能性があるため、システムはこのデータをすぐに保存します。

毎回データを保存することで、バンドル内の更新されたデータを必要に応じて復元できます。

  1. MainActivity で、onSaveInstanceState() コールバックをオーバーライドして、ログ ステートメントを追加します。
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Log.d(TAG, "onSaveInstanceState Called")
}
  1. アプリをコンパイルして実行し、ホームボタンをクリックしてバックグラウンドに移行します。onSaveInstanceState() コールバックは、onPause()onStop() の直後に発生します。
2020-10-16 11:05:21.726 23415-23415/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:05:22.382 23415-23415/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:05:22.393 23415-23415/com.example.android.dessertclicker D/MainActivity: onSaveInstanceState Called
  1. ファイルの先頭にあるクラス定義の直前に、次の定数を追加します。
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"

これらのキーは、インスタンスの状態バンドルからのデータの保存と取得の両方に使用します。

  1. onSaveInstanceState() まで下にスクロールして、outState パラメータ(Bundle 型)を見つけます。

Bundle は Key-Value ペアのコレクションで、キーは常に文字列です。バンドルには、Int 値や Boolean 値などの単純なデータを追加できます。このバンドルはメモリに保持されるため、バンドルのデータは小さくすることをおすすめします。デバイスによって異なりますが、バンドルのサイズにも制限があります。保存するデータが多すぎると、TransactionTooLargeException エラーが発生してアプリがクラッシュするリスクがあります。5. onSaveInstanceState() で、putInt() メソッドを使用して revenue 値(整数)をバンドルに入れます。

outState.putInt(KEY_REVENUE, revenue)

putInt() メソッド(および putFloat()putString() などの Bundle クラスの同様のメソッド)は、キーの文字列(KEY_REVENUE 定数)と、実際に保存する値の、2 つの引数を取ります。

  1. デザートの販売数についても、同じ手順を繰り返します。
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)

onCreate() を使用してバンドルデータを復元する

アクティビティの状態は、onCreate(Bundle) または onRestoreInstanceState(Bundle) で復元できます(onSaveInstanceState() メソッドによって入力された Bundle が、両方のライフサイクル コールバック メソッドに渡されます)。

  1. onCreate() まで下にスクロールして、メソッドのシグネチャを確認します。
override fun onCreate(savedInstanceState: Bundle) {

onCreate() は呼び出されるたびに Bundle を取得します。プロセスのシャットダウンによってアクティビティが再起動されると、保存したバンドルは onCreate() に渡されます。アクティビティが新しく開始された場合は、onCreate()Bundlenull になります。そのため、バンドルが null でない場合は、既知のポイントからアクティビティを「再作成」していることがわかります。

  1. 次のコードを onCreate() の、binding 変数の設定の後に追加します。
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

null のテストでは、バンドル内にデータがあるかどうか、またはバンドルが null であるかどうかを判定します。これにより、アプリが新規に起動されたか、シャットダウン後に再作成されたがわかります。このテストは、バンドルからデータを復元する際に使用される一般的なパターンです。

ここで使用したキー(KEY_REVENUE)は、putInt() に使用したものと同じです。毎回同じキーを使用するように、それらのキーを定数として定義することをおすすめします。putInt() を使用してバンドルにデータを格納したのと同じように、getInt() を使用してバンドルからデータを取得します。getInt() メソッドは次の 2 つの引数を取ります。

  • キーとして機能する文字列(収益値の "key_revenue" など)。
  • バンドル内のキーに対する値が存在しない場合のデフォルト値。

バンドルから取得した整数が revenue 変数に割り当てられ、UI でその値が使用されます。

  1. getInt() メソッドを追加して、収益とデザートの販売数を復元します。
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
}
  1. アプリをコンパイルして実行します。ドーナツに切り替わるまでカップケーキを 5 回以上押します。
  2. デバイスを回転させます。今回は収益とデザート販売数の正しい値がバンドルから取得され、アプリに表示されます。ただし、デザートがカップケーキに戻っています。4179956182ffc634.png アプリがシャットダウンから復帰したときに元の状態が正確に復元されるように、次のことを行います。
  3. MainActivityshowCurrentDessert() メソッドを調べます。このメソッドは、現在のデザート販売数と allDesserts 変数内のデザートのリストに基づいて、アクティビティに表示するデザートの画像を決定します。
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

このメソッドは、適切な画像を選択するためにデザートの販売数に依存しています。したがって、onSaveInstanceState() のバンドル内に画像への参照を格納するために何かを行う必要はありません。このバンドルには、すでにデザートの販売数を格納しています。

  1. onCreate() の、バンドルから状態を復元するブロックで、showCurrentDessert() を呼び出します。
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   showCurrentDessert()
}
  1. アプリをコンパイルして実行し、画面を回転させます。デザート販売数と総収益の値、およびデザートの画像が正しく復元されるようになりました。

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

  • アクティビティのライフサイクルは、アクティビティが移行する一連の状態です。アクティビティのライフサイクルは、アクティビティが最初に作成された時点で開始され、アクティビティが破棄されると終了します。
  • ユーザーがアクティビティ間やアプリの内外に移動すると、各アクティビティはライフサイクルの状態間を移行します。
  • アクティビティのライフサイクルの各状態には、Activity クラスでオーバーライド可能な、対応するコールバック メソッドがあります。ライフサイクル メソッドのコアセットは、onCreate()onStart()onPause()onRestart()onResume()onStop()onDestroy() です。
  • アクティビティがあるライフサイクル状態に移行したときに実行される動作を追加するには、その状態のコールバック メソッドをオーバーライドします。
  • Android Studio でクラスにスケルトン オーバーライド メソッドを追加するには、[Code] > [Override Methods] を選択するか、Control+o(Mac の場合は Command+o)を押します。

Log を使用したロギング

  • Android Logging API(具体的には Log クラス)を使用すると、Android Studio の Logcat に表示される短いメッセージを記述できます。
  • Log.d() を使用してデバッグ メッセージを記述します。このメソッドは、ログタグ(通常はクラスの名前)とログメッセージ(短い文字列)の 2 つの引数を取ります。
  • Android Studio の [Logcat] ウィンドウで、システムログ(自分で記述したメッセージを含む)を表示します。

アクティビティの状態の保存

  • アプリがバックグラウンドに移行する際(onStop() が呼び出された直後)、アプリデータをバンドルに保存できます。一部のアプリデータ(EditText のコンテンツなど)は自動的に保存されます。
  • バンドルは、キーと値のコレクションである Bundle のインスタンスです。キーは常に文字列です。
  • onSaveInstanceState() コールバックを使用すると、アプリが自動的にシャットダウンされた場合でも、保持したい他のデータをバンドルに保存できます。バンドルにデータを取り込むには、put で始まるバンドル メソッド(putInt() など)を使用します。
  • onRestoreInstanceState() メソッド(より一般的には onCreate())内のバンドルからデータを復元できます。onCreate() メソッドには、バンドルを保持する savedInstanceState パラメータがあります。
  • savedInstanceState 変数が null の場合、アクティビティは状態バンドルなしで開始されたため、取得する状態データはありません。
  • キーを使用してバンドルからデータを取得するには、get で始まる Bundle メソッド(getInt() など)を使用します。

構成の変更

  • 構成の変更は、デバイスの状態が大きく変更されたために、システムにとってアクティビティを破棄して再構築するのが最も手間がかからない場合に行われます。
  • 構成の変更が発生する最も一般的な例は、ユーザーがデバイスを縦向きから横向きに、または横向きから縦向きにした場合です。デバイスの言語が変更された場合や、ハードウェア キーボードが接続された場合も、構成の変更が発生する可能性があります。
  • 構成の変更が発生すると、Android はすべてのアクティビティ ライフサイクルのシャットダウン コールバックを呼び出します。その後、Android はアクティビティをゼロから再起動して、すべてのライフサイクルの起動コールバックを実行します。
  • 構成の変更により Android がアプリをシャットダウンする際に、onCreate() が使用可能な状態バンドルを持つアクティビティを再起動します。
  • プロセスのシャットダウンと同様に、アプリの状態を onSaveInstanceState() のバンドルに保存します。