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: DessertTimer を設定する

  1. 前回の Codelab で作成した DessertClicker アプリを開きます。(アプリがない場合は、こちらから DessertClickerLogs をダウンロードできます)。
  2. [Project] ビューで、[java > 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. onStart() から onCreate()startTimer() 呼び出しを切り取って貼り付けます。この変更は、onCreate() を使用してリソースを初期化し、onStart() を使用してリソースを開始するのではなく、onCreate() でリソースを初期化して開始するケースを示しています。
  3. アプリをコンパイルして実行します。タイマーが想定どおりに動作していることを確認します。
  4. [Home] をクリックしてアプリを停止します。タイマーは想定どおりに停止します。
  5. [最近] 画面を使用してアプリに戻ります。この場合、タイマーは再開されません。onCreate() はアプリの起動時にのみ呼び出され、アプリがフォアグラウンドに戻ったときには呼び出されないためです。

要点:

  • ライフサイクル コールバックでリソースを設定する場合は、リソースを破棄します。
  • 対応するメソッドで設定と破棄を行います。
  • onStart() で設定したものは、onStop() で停止または削除します。

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

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

Android Jetpack の一部であるライフサイクル ライブラリを使用すると、このタスクを簡素化できます。このライブラリは、多くの可動部分をトラッキングする必要があり、その一部が異なるライフサイクル状態にある場合に特に便利です。このライブラリは、ライフサイクルの動作を逆転させます。通常は、アクティビティまたはフラグメントがコンポーネント(DessertTimer など)にライフサイクル コールバックが発生したときの処理を指示します。ライフサイクル ライブラリを使用すると、コンポーネント自体がライフサイクルの変化を監視し、変化が発生したときに必要な処理を行います。

ライフサイクル ライブラリには、次の 3 つの主要な部分があります。

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

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

ステップ 1: DessertTimer を LifecycleObserver に変換する

ライフサイクル ライブラリの重要な部分に、ライフサイクル オブザーベーションというコンセプトがあります。オブザベーションにより、クラス(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() メソッドで、this.lifecycle を含めるように DessertTimer の初期化を変更します。
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 OS がシャットダウンしたアプリにユーザーが戻ると、Android はそのアプリを再起動します。

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

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

Android Debug Bridge(adb)は、パソコンに接続されたエミュレータやデバイスに指示を送信できるコマンドライン ツールです。この手順では、adb を使用してアプリのプロセスを閉じ、Android がアプリをシャットダウンしたときに何が起こるかを確認します。

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

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

このコマンドは、接続されているデバイスまたはエミュレータに対し、アプリがバックグラウンドにある場合にのみ、dessertclicker パッケージ名のプロセスを停止するよう指示します。アプリがバックグラウンドにあったため、プロセスが停止したことを示すものはデバイスやエミュレータの画面に表示されません。Android Studio で [Run] タブをクリックすると、「Application terminated.」というメッセージが表示されます。[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() 呼び出しは、アクティビティがフォアグラウンドを終了したときに、少量の情報をバンドルに保存するための安全策として考えることができます。アプリがシャットダウンされるまで待機するとリソース不足になる可能性があるため、システムはこのデータをすぐに保存します。毎回データを保存することで、バンドル内の更新されたデータを必要に応じて復元できます。

  1. MainActivity で、onSaveInstanceState() コールバックをオーバーライドして、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 KB よりもはるかに少ないデータを保存する必要があります。そうしないと、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. 次のコードを onCreate()DessertTimer の設定の後に追加します。
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. MainActivityonSaveInstanceState() メソッドのコメントを解除し、アプリを実行して、カップケーキをクリックし、アプリまたはデバイスを回転させます。今回は、アクティビティの回転後もデザート データが保持されていることに注意してください。

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 のランディング ページをご覧ください。