Android Kotlin の基礎 04.2: ライフサイクルの複雑な状況

この Codelab は、Android Kotlin の基礎コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できます。すべてのコース Codelab は Android Kotlin の基礎 Codelab ランディング ページに掲載されています。

はじめに

前回の Codelab では、ActivityFragment のライフサイクルについて学習し、アクティビティとフラグメントでライフサイクルの状態が変化したときに呼び出されるメソッドを確認しました。この Codelab では、アクティビティのライフサイクルについて詳しく説明します。また、整理されたシンプルなコードでメンテナンス イベントのライフサイクルの管理に役立つ Android Jetpack ライフサイクル ライブラリについても学習します。

前提となる知識

  • アクティビティの概要と、アプリでアクティビティを作成する方法。
  • ActivityFragment のライフサイクルの基本と、アクティビティが状態間で遷移したときに呼び出されるコールバック。
  • onCreate()onStop() のライフサイクル コールバック メソッドをオーバーライドして、アクティビティまたはフラグメントのライフサイクルのさまざまなタイミングでオペレーションを実行する方法。

ラボの内容

  • ライフサイクル コールバックでアプリの一部を設定、開始、停止する方法。
  • Android ライフサイクル ライブラリを使用してライフサイクル オブザーバーを作成し、アクティビティとフラグメントのライフサイクルを管理しやすくする方法
  • Android プロセスのシャットダウンがアプリのデータにどのように影響するか、および Android がアプリを閉じたときにデータを自動的に保存および復元する方法。
  • デバイスの回転などの設定変更が、ライフサイクルの状態の変化につながり、アプリの状態に影響を及ぼす仕組みです。

演習内容

  • DessertClicker アプリを変更して、タイマー関数を追加し、アクティビティのライフサイクルのさまざまなタイミングでそのタイマーを開始、停止します。
  • Android ライフサイクル ライブラリを使用するようにアプリを変更し、DessertTimer クラスをライフサイクル オブザーバーに変換します。
  • Android Debug Bridge(adb)をセットアップして使用し、アプリのプロセスのシャットダウンとその後に発生するライフサイクル コールバックをシミュレートします。
  • アプリが予期せず閉じられた場合に失われる可能性があるアプリデータを保持する onSaveInstanceState() メソッドを実装します。アプリを再起動したときにそのデータを復元するコードを追加します。

この Codelab では、前の Codelab の DessertClicker アプリを拡張します。バックグラウンド タイマーを追加して、Android ライフサイクル ライブラリを使用するようにアプリを変換します。

前の Codelab では、さまざまなライフサイクル コールバックをオーバーライドし、システムがこれらのコールバックを呼び出したときにロギングすることで、アクティビティとフラグメントのライフサイクルを監視する方法を学習しました。このタスクでは、DessertClicker アプリでライフサイクル タスクを管理する複雑な例を紹介します。タイマーを 1 秒ごとに実行し、ログの秒数を出力するタイマーを使用します。

ステップ 1: Dessert タイマーを設定する

  1. 前回の Codelab の DessertClicker アプリを開きます。(アプリがインストールされていない場合は、DessertClickerLogs をダウンロードできます)
  2. [Project] ビューで、ava > com.example.android.dessertclicker を展開し、DessertTimer.kt を開きます。現在、すべてのコードがコメントアウトされているため、アプリの一部として実行されません。
  3. エディタ ウィンドウでコードをすべて選択します。[Code > Comment with Line Comment] を選択するか、Control+/(Mac では Command+/)キーを押します。このコマンドは、ファイル内のすべてのコードのコメントを解除します。(アプリを再構築するまで、Android Studio は未解決の参照エラーを表示することがあります)。
  4. DessertTimer クラスには、タイマーを開始および停止する startTimer()stopTimer() が含まれています。startTimer() が実行されている場合、タイマーはログメッセージを 1 秒ごとに出力し、その時間の合計秒数が表示されます。その後、stopTimer() メソッドはタイマーとログ ステートメントを停止します。
  1. MainActivity.kt を開きます。クラスの先頭にある dessertsSold 変数のすぐ下に、タイマーの変数
    を追加します。
private lateinit var dessertTimer : DessertTimer;
  1. onCreate() まで下にスクロールして、setOnClickListener() 呼び出しの直後に新しい DessertTimer オブジェクトを作成します。
dessertTimer = DessertTimer()


これでデザート タイマー オブジェクトが作成されました。次に、アクティビティが画面上のときにのみ実行されるように、タイマーを開始および停止する場所を検討します。次のステップでいくつかのオプションを見ていきます。

ステップ 2: タイマーを開始、停止する

onStart() メソッドは、アクティビティが表示される直前に呼び出されます。アクティビティが表示されなくなったら、onStop() メソッドが呼び出されます。こうしたコールバックは、タイマーを開始、停止するタイミングとしては妥当です。

  1. MainActivity クラスの onStart() コールバックでタイマーを開始します(
    )。
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. onStop()のタイマーを止めて:
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. アプリをコンパイルして実行します。Android Studio で [Logcat] ペインをクリックします。[Logcat] 検索ボックスに「dessertclicker」と入力し、MainActivity クラスと DessertTimer クラスの両方でフィルタします。アプリが起動するとタイマーがすぐに始まります。
  2. [戻る] ボタンをクリックするとタイマーが停止します。アクティビティと制御するタイマーの両方が破棄されているため、タイマーが停止しています。
  3. [最近] 画面を使用してアプリに戻ります。Logcat を確認すると、タイマーが 0 から再開していることがわかります。
  4. [共有] ボタンをクリックします。Logcat で、タイマーがまだ実行中であることに注目してください。

  5. [ホーム] ボタンをクリックします。Logcat を見ると、タイマーが止まっていることがわかります。
  6. [最近] 画面を使用してアプリに戻ります。Logcat を確認すると、タイマーが前回終了したところから再開していることがわかります。
  7. MainActivityonStop() メソッドで、stopTimer() の呼び出しをコメントアウトします。stopTimer() をコメントアウトすると、onStart() でオペレーションを開始したが、onStop() でオペレーションを再開するのを忘れてしまう場合があります。
  8. アプリをコンパイルして実行し、タイマーが開始したらホームボタンをクリックします。アプリがバックグラウンドで動作している場合でも、タイマーは動作し、システム リソースを継続的に使用します。タイマーが動作し続けることは、アプリのメモリリークであり、想定した動作ではありません。

    一般的には、コールバックで何かをセットアップまたは開始したとき、それに対応するコールバックで停止または削除します。こうすることで、不要になったときに何も実行する必要がなくなります。
  1. タイマーを停止する onStop() の行のコメント化を解除します。
  2. startTimer() の呼び出しを切り取って onStart() から onCreate() に貼り付けます。この変更は、リソースを初期化するために onCreate() を使用し、開始するために onStart() を使用するのではなく、onCreate() でリソースを初期化して開始する場合を示しています。
  3. アプリをコンパイルして実行します。想定どおりにタイマーの実行が開始されます。
  4. [ホーム] をクリックしてアプリを停止します。タイマーは想定どおりに動作しなくなります。
  5. [最近] 画面を使用してアプリに戻ります。この場合、タイマーは再開されません。これは、onCreate() はアプリが起動時にのみ呼び出され、アプリがフォアグラウンドに戻っても呼び出されないためです。

要点:

  • ライフサイクル コールバックでリソースを設定する際は、そのリソースを破棄します。
  • 対応するメソッドでセットアップと破棄を行います。
  • onStart() にセットアップした場合は、onStop() で停止または破棄してください。

DessertClicker アプリでは、onStart() でタイマーを開始した場合、onStop() でタイマーを停止する必要があることがわかります。タイマーは 1 つしかないので、タイマーを止めることは簡単ではありません。

より複雑な Android アプリでは、onStart() または onCreate() で多くのものを設定し、それらを onStop() または onDestroy() で破棄することもできます。たとえば、アニメーション、音楽、センサー、タイマーなどをセットアップして破棄し、開始と停止の両方を行う必要があるとします。パスワードを忘れた場合は、バグや頭痛の種につながります。

この処理は、Android Jetpack の一部であるライフサイクル ライブラリによって簡略化されます。このライブラリは、多数の可変部分(一部は異なるライフサイクル状態にある)を追跡する必要がある場合に特に便利です。ライブラリは、ライフサイクルの仕組みを反転します。通常、アクティビティまたはフラグメントは、ライフサイクル コールバックの発生時に行う処理をコンポーネント(DessertTimer など)に指示します。ライフサイクル ライブラリを使用する場合は、コンポーネント自体でライフサイクルの変更を監視し、その変更が発生した時点で必要な処理を行います。

ライフサイクル ライブラリは、次の 3 つの主要部分で構成されています。

  • ライフサイクル所有者。ライフサイクルを持つコンポーネント(つまり「所有」)。ActivityFragment はライフサイクルのオーナーです。ライフサイクル所有者は、LifecycleOwner インターフェースを実装します。
  • Lifecycle クラス: ライフサイクル所有者の実際の状態を保持し、ライフサイクルの変更が発生したときにイベントをトリガーします。
  • ライフサイクル オブザーバー。ライフサイクルの状態を監視し、ライフサイクルが変更されたときにタスクを実行します。ライフサイクル オブザーバーは LifecycleObserver インターフェースを実装しています。

このタスクでは、Android ライフサイクル ライブラリを使用するよう DessertClicker アプリを変換し、そのライブラリによって Android アクティビティとフラグメントのライフサイクルが管理しやすくなる方法を学習します。

ステップ 1: Dessert タイマーを LifecycleObserver にする

ライフサイクル ライブラリの重要な部分は、ライフサイクルのモニタリングのコンセプトです。Observation を使用すると、クラス(DessertTimer など)はアクティビティまたはフラグメントのライフサイクルを認識し、そのライフサイクルの状態の変化に対応して開始および停止できます。ライフサイクル オブザーバーを使うと、アクティビティ メソッドやフラグメント メソッドからオブジェクトを開始、停止する手間を省くことができます。

  1. DesertTimer.kt クラスを開きます。
  2. DessertTimer クラスのクラス シグネチャを次のように変更します。
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

この新しいクラス定義は次の 2 つのことを行います。

  • コンストラクタは Lifecycle オブジェクトを受け取ります。このオブジェクトはタイマーが監視しているライフサイクルです。
  • クラス定義は LifecycleObserver インターフェースを実装しています。
  1. runnable 変数の下で、クラス定義に init ブロックを追加します。init ブロックで、addObserver() メソッドを使用して、オーナー(アクティビティ)から渡されたライフサイクル オブジェクトをこのクラス(オブザーバー)に接続します。
 init {
   lifecycle.addObserver(this)
}
  1. startTimer()@OnLifecycleEvent annotation アノテーションを付け、ON_START ライフサイクル イベントを使用します。ライフサイクル オブザーバーが監視可能なライフサイクル イベントはすべて、Lifecycle.Event クラスに含まれます。
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. ON_STOP イベントを使用して、stopTimer() にも同じ操作を行います。
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

ステップ 2: MainActivity を変更する

FragmentActivity クラスは LifecycleOwner を実装しているため、MainActivity クラスはすでに継承によりライフサイクルのオーナーになっています。したがって、アクティビティのライフサイクルを認識させるために必要な操作はありません。アクティビティのライフサイクル オブジェクトを DessertTimer コンストラクタに渡すだけです。

  1. MainActivity を開きます。onCreate() メソッドで、DessertTimer の初期化を変更して this.lifecycle を含めます。
dessertTimer = DessertTimer(this.lifecycle)

アクティビティの lifecycle プロパティは、このアクティビティが所有する Lifecycle オブジェクトを保持します。

  1. onCreate()startTimer() の呼び出しと、onStop()stopTimer() の呼び出しを削除します。DessertTimer がライフサイクル自体を監視するようになり、ライフサイクルの状態が変化すると自動的に通知されるため、アクティビティの処理方法を DessertTimer に指定する必要はありません。これらのコールバックで行うのは、メッセージをログに記録することだけです。
  2. アプリをコンパイルして実行し、Logcat を開きます。想定どおりにタイマーの実行が開始されました。
  3. アプリをバックグラウンドに移行するには、ホームボタンをクリックします。タイマーは想定どおりに動作を停止しています。

バックグラウンドで Android がアプリをシャットダウンした場合、アプリとそのデータはどうなりますか?このエッジの厄介なケースは理解しておく必要があります。

アプリがバックグラウンドに移行しても、破棄されず、ユーザーが復帰するのを待機するだけです。ただし、Android OS の主な懸念事項の 1 つは、フォアグラウンドでのアクティビティをスムーズに動作させることです。たとえば、ユーザーがバスに乗るために GPS アプリを使用している場合、GPS アプリをすばやく表示してルートを表示し続けることが重要です。ユーザーが数日間見ていない DessertClicker アプリをバックグラウンドでスムーズに実行し続けることは、それほど重要ではありません。

Android では、フォアグラウンド アプリが問題なく動作するように、バックグラウンド アプリが規制されます。たとえば Android では、バックグラウンドで動作するアプリの処理量が制限されます。

Android は、アプリに関連付けられたすべてのアクティビティを含むアプリプロセス全体をシャットダウンすることもあります。Android は、システムに負荷がかかり、視覚的に遅れる危険性がある場合に、このようなシャットダウンを行うため、この時点で追加のコールバックやコードの実行はありません。アプリのプロセスはバックグラウンドでそのままシャットダウンされますが、特に通知されることはないため、ユーザーにはアプリが終了したようには見えません。ユーザーが Android OS がシャットダウンしたアプリに戻ると、Android はそのアプリを再起動します。

このタスクでは、Android プロセスのシャットダウンをシミュレートし、アプリが再起動したときにどうなるかを調べます。

ステップ 1: adb を使用してプロセスのシャットダウンをシミュレートする

Android Debug Bridge(adb)は、パソコンにアタッチされているエミュレータとデバイスに手順を送信できるコマンドライン ツールです。このステップでは、adb を使用してアプリのプロセスを終了し、Android がアプリをシャットダウンするとどうなるかを確認します。

  1. アプリをコンパイルして実行します。カップケーキを数回クリックします。
  2. ホームボタンを押し、アプリをバックグラウンドに移行します。アプリが停止し、Android がアプリで使用しているリソースを必要とする場合、アプリは閉じられる場合があります。
  3. Android Studio で [Terminal] タブをクリックしてコマンドライン ターミナルを開きます。
  4. adb」と入力して Enter キーを押します。

    Android Debug Bridge version X.XX.X で始まり tags to be used by logcat (see logcat —help で終わる出力が多数ある場合は、何も問題ありません。代わりに adb: command not found が表示された場合は、実行パスで adb コマンドが使用可能であることを確認してください。手順については、ユーティリティの章で「adb を実行パスに追加する」をご覧ください。
  5. このコメントをコピーしてコマンドラインに貼り付け、Enter キーを押します。
adb shell am kill com.example.android.dessertclicker

このコマンドは、接続されたデバイスまたはエミュレータに対し、アプリがバックグラウンドで動作している場合にのみ、dessertclicker パッケージ名でプロセスを停止するように指示します。アプリがバックグラウンドで動作していたため、デバイスまたはエミュレータの画面に、プロセスが停止したことを示すものは表示されません。Android Studio で [Run] タブをクリックして、「Application completed."」というメッセージを確認します。[Logcat] タブをクリックして、onDestroy() コールバックが実行されていないことを確認します(アクティビティは単に終了しています)。

  1. [最近] 画面を使用してアプリに戻ります。アプリがバックグラウンドになったか、完全に停止したかにかかわらず、[最近] に表示されます。[最近] 画面を使用してアプリに戻ると、アクティビティが再開されます。アクティビティは、onCreate() を含め、起動ライフサイクル コールバック全体を通過します。
  2. アプリの再起動時に、「スコア」とデザートの合計金額の両方がデフォルト値(0)にリセットされます。Android がアプリをシャットダウンした場合、状態が保存されなかった理由は何ですか。

    OS がアプリを再起動すると、Android は可能な限り前の状態にリセットしようとします。Android では、アクティビティから離れるたびに、一部のビューの状態を取得し、それをバンドルに保存します。自動的に保存されるデータの例には、EditText のテキスト(レイアウトに ID が設定されているもの)とアクティビティのバックスタックがあります。

    ただし、Android OS はすべてのデータについて把握していない場合があります。たとえば、DessertClicker アプリに revenue のようなカスタム変数がある場合、Android OS はこのデータやアクティビティにとっての重要度を認識しません。このデータは手動でバンドルに追加する必要があります。

ステップ 2: onSaveInstanceState() を使用してバンドルデータを保存する

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

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

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

   Timber.i("onSaveInstanceState Called")
}
  1. アプリをコンパイルして実行し、ホームボタンをクリックしてバックグラウンドに移行します。onSaveInstanceState() コールバックは、onPause()onStop() の直後に発生します。
  2. ファイルの先頭にあるクラス定義の直前に、次の定数を追加します。
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

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

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

    バンドルは Key-Value ペアのコレクションで、キーは常に文字列です。int 値や boolean 値などのプリミティブ値をバンドルに追加できます。
    このバンドルは RAM 内に保持されるため、バンドルのデータは小さくすることをおすすめします。デバイスによって異なりますが、バンドルのサイズにも制限があります。一般的には 100, 000 行未満で保存する必要があります。そうしないと、TransactionTooLargeException エラーでアプリがクラッシュするリスクがあります。
  2. onSaveInstanceState() で、putInt() メソッドを使用して revenue 値(整数)をバンドルに入れます。
outState.putInt(KEY_REVENUE, revenue)

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

  1. デザートの販売数とタイマーのステータスについて、同じプロセスを繰り返します。
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

ステップ 3: onCreate() を使用してバンドルデータを復元する

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

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

  1. DessertTimer の設定後に、onCreate() にコード
    を追加します。
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)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. アプリをコンパイルして実行します。ドーナツに切り替わるまでカップケーキを 5 回以上押します。[ホーム] をクリックすると、アプリがバックグラウンドに移動します。
  2. Android Studio の [Terminal] タブで adb を実行してアプリのプロセスをシャットダウンします。
adb shell am kill com.example.android.dessertclicker
  1. [最近] 画面を使用してアプリに戻ります。今回は、バンドルの収益とデザート販売額が正しい値で返されます。ただし、デザートがカップケーキに戻っています。アプリがシャットダウンから復帰したときに元の状態が正確に復元されるように、次のことを行います。
  2. 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)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. アプリをコンパイルして実行し、バックグラウンドに移行します。adb を使用してプロセスをシャットダウンします。[最近] 画面を使用してアプリに戻ります。デザートの総計、総収益、デザートの画像が再び正しく表示されています。

アクティビティとフラグメントのライフサイクルを管理する際の最後の重要なケースとして、構成の変更がアクティビティとフラグメントのライフサイクルに与える影響が挙げられます。

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

ステップ 1: デバイスの回転とライフサイクル コールバックを調べる

  1. アプリをコンパイルして実行し、Logcat を開きます。
  2. デバイスまたはエミュレータを回転させて横向きにします。エミュレータを左右に回転させるには、回転ボタン、または Control キーと矢印キー(Mac では Command キーと矢印キー)を使用します。
  3. Logcat で出力を調べます。MainActivity の出力をフィルタします。
    デバイスまたはエミュレータの画面を回転させると、すべてのライフサイクル コールバックが呼び出され、アクティビティがシャットダウンされます。その後、アクティビティが再作成されると、すべてのライフサイクル コールバックが呼び出され、アクティビティが開始されます。
  4. MainActivity で、onSaveInstanceState() メソッド全体をコメントアウトします。
  5. アプリを再度コンパイルして実行します。カップケーキを数回クリックし、デバイスまたはエミュレータを回転させます。今回は、デバイスが回転してアクティビティがシャットダウンおよび再作成されると、アクティビティはデフォルト値で開始されます。

    構成の変更が発生すると、Android は前のタスクで説明したものと同じインスタンス状態バンドルを使用して、アプリの状態を保存し、復元します。プロセスのシャットダウンと同様に、onSaveInstanceState() を使用してアプリのデータをバンドルに入れます。次に、onCreate() でデータを復元して、デバイスが回転してもアクティビティの状態データが失われないようにします。
  6. MainActivity で、onSaveInstanceState() メソッドのコメント化を解除し、アプリを実行して、カップケーキをクリックして、アプリまたはデバイスを回転させます。今度は、デザート データがアクティビティのローテーション中も保持されています。

Android Studio プロジェクト: DessertClickerFinal

ライフサイクルに関するヒント

  • ライフサイクル コールバックでセットアップまたは開始する場合は、対応するコールバックで停止または削除します。停止することで、不要になったときに実行し続ける必要がなくなります。たとえば、onStart() でタイマーを設定している場合は、onStop() でタイマーを一時停止または停止する必要があります。
  • onCreate() は、アプリの初回起動時に 1 回だけ実行される部分を初期化するためにのみ使用します。onStart() を使用して、アプリの起動時とフォアグラウンドの復帰時に実行されるアプリの部分を開始します。

Lifecycle ライブラリ

  • Android ライフサイクル ライブラリを使用することで、アクティビティやフラグメントからライフサイクル対応が必要な実際のコンポーネントにライフサイクルの制御を移行できます。
  • ライフサイクル所有者は、ActivityFragment など、ライフサイクルを持つ(つまり「所有される」)コンポーネントです。ライフサイクル所有者は、LifecycleOwner インターフェースを実装します。
  • ライフサイクル オブザーバーは、現在のライフサイクルの状態に注意を払い、ライフサイクルが変更されたときにタスクを実行します。ライフサイクル オブザーバーは LifecycleObserver インターフェースを実装しています。
  • Lifecycle オブジェクトには、実際のライフサイクルの状態が含まれており、ライフサイクルが変更されたときにイベントをトリガーします。

ライフサイクル対応クラスを作成するには:

  • ライフサイクル対応が必要なクラスに LifecycleObserver インターフェースを実装します。
  • アクティビティまたはフラグメントのライフサイクル オブジェクトを使用して、ライフサイクル オブザーバー クラスを初期化します。
  • ライフサイクル オブザーバー クラスで、対象のライフサイクルの状態の変化にライフサイクル対応メソッドのアノテーションを付けます。

    たとえば、@OnLifecycleEvent(Lifecycle.Event.ON_START) アノテーションは、メソッドが onStart ライフサイクル イベントを監視していることを示します。

シャットダウンの処理とアクティビティの状態の保存

  • Android は、フォアグラウンドで実行されるアプリが問題なく動作するように、バックグラウンドで実行されるアプリを制御します。この規制には、バックグラウンドのアプリの処理量を制限することや、場合によってはアプリ プロセス全体をシャットダウンすることも含まれます。
  • ユーザーは、システムがバックグラウンドでアプリをシャットダウンしたかどうかを判断できません。アプリがまだ [最近] 画面に表示され、ユーザーが移動したときと同じ状態で再起動する。
  • Android Debug Bridge(adb)は、パソコンにアタッチされているエミュレータとデバイスに手順を送信できるコマンドライン ツールです。adb を使用すると、アプリ内のプロセスのシャットダウンをシミュレートできます。
  • Android がアプリプロセスをシャットダウンする際、onDestroy() ライフサイクル メソッドは呼び出されません。アプリは停止するだけです。

アクティビティとフラグメントの状態の保持

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

構成の変更

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

Udacity コース:

Android デベロッパー ドキュメント:

その他:

このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。

  • 必要に応じて課題を割り当てます。
  • 宿題の提出方法を生徒に伝える。
  • 宿題を採点します。

教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。

この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。

アプリを変更する

レッスン 1 で生成した DiceRoller アプリを開きます。(アプリがインストールされていない場合は、こちらからダウンロードできます)。アプリをコンパイルして実行します。デバイスを回転させると、サイコロの現在の値が失われます。onSaveInstanceState() を実装してその値をバンドルに保持し、その値を onCreate() に復元します。

次の質問に答えてください。

問題 1

お客様のアプリには、表示のために大量の計算を必要とする物理シミュレーションが含まれています。ユーザーに電話がかかってきたときの処理として、正しいものは次のうちどれですか。

  • 通話中は、物理シミュレーションでの物体の位置を計算し続ける必要があります。
  • 通話中は、物理シミュレーションでのオブジェクトの位置計算を停止する必要がある。

質問 2

アプリが画面に表示されていないときにシミュレーションを一時停止するには、どのライフサイクル メソッドをオーバーライドする必要がありますか。

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

問題 3

Android ライフサイクル ライブラリを介してクラスをライフサイクル対応にするには、クラスでどのインターフェースを実装する必要がありますか。

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

問題 4

アクティビティの onCreate() メソッドが、データを含む Bundle(つまり、Bundlenull ではない)を受け取るのは、どのような状況ですか。この場合、複数の回答が当てはまる可能性があります。

  • アクティビティは、デバイスが回転した後で再起動されます。
  • アクティビティはゼロから開始されます。
  • バックグラウンドから復帰した後、アクティビティが再開される。
  • デバイスが再起動された。

次のレッスンを開始する: 5.1: ViewModel と ViewModelFactory

このコースの他の Codelab へのリンクについては、Android Kotlin の基礎 Codelab ランディング ページをご覧ください。