iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day14【Complex lifecycle situations編】

学ぶこと

  • ライフサイクルコールバックでアプリの一部を設定、開始、停止する方法
  • Android ライフサイクルライブラリを使用してライフサイクルオブザーバーを作成し、activity と fragment のライフサイクルを管理しやすくする方法
  • Android プロセスのシャットダウンがアプリ内のデータに与える影響、および Android アプリを閉じた時にそのデータを自動的に保存および復元する方法
  • バイスの rotation やその他の configuration がライフサイクルの状態にどのような変化をもたらし、アプリの状態に影響を与えるのか

すること

  • DessertClicker アプリを変更してタイマー機能を含め、Activity ライフサイクルの様々な時点でそのタイマーを開始および停止します

  • Android ライフサイクルライブラリを使用するようにアプリを変更して、DessertTimer クラスをライフサイクルオブザーバーに変換します

  • Android Debug Bridge(abc)を設定して使用し、アプリのプロセスシャットダウンとその時に発生するライフサイクルコールバックをシュミレートします

  • onSaveInstanceState() メソッドを実装して、アプリが予期せず閉じられた場合に失われる可能性のあるアプリデータに保存します。アプリの再起動時にそのデータを復元するコードを追加します

アプリ概要

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

ライフサイクルの間違いを避ける

前回のコードラボでは、様々なライフサイクルコールバックをオーバーライドし、システムがそれらのコールバックを呼び出す時にログに記録することで、activity と fragment のライフサイクルを監視する方法を学習しました。このタスクでは、DessertClicker アプリでライフサイクルタスクを管理するより複雑な例について説明します。実行されているカウントとともに、ログステートメントを毎秒出力するタイマーを使用します。

DessertTimer を設定する

  1. DessertClicker アプリを開きます。

  2. java > com.example.android.dessertclicker を展開し、DessertTimer.kt を開きます。現在、全てのコードがコメントアウトされているため、アプリの一部として実行されないことに注意してください

  3. Code > Comment with Line Comment を選択するか、Command + / を押します。このコマンドは、ファイル内の全てのコードのコメントを解除します。(AndroidStudio では、アプリを再構築するまで未解決の参照エラーが表示される場合があります。)

  4. DessertTimer クラスには、タイマーを開始および停止する startTimer() および stopTimer() が含まれていることに注意してください。startTimer() が実行されている場合、タイマーは1秒ごとにログメッセージを出力し、その時間の合計秒数を示します。次に、stopTimer() メソッドは、タイマーとログステートメントを停止します。

Note: DessertTimer クラスは、タイマーにバックグランドスレッドを使用し、Runable クラスと Handler クラスが関連づけられています。このコードラボでは、これらのことについて知る必要はありません(ただし、後のコードラボでスレッドについて詳しく学習します)。

  1. MainActivity.kt を開きます。クラスの最上部、dessertsSold 変数のすぐ下に、タイマーの変数を追加します。
private lateinit var dessertTimer : DessertTimer;
  1. setOnClickListener() を呼び出した直後に、onCreate() まで下にスクロールして、新しい DessertTimer オブジェクトを作成します。
dessertTimer = DessertTimer()

dessert timer object ができたので、activity が画面に表示されている時にのみタイマーを実行するために、タイマーを開始および停止する場所を検討します。次のステップでいくつかのオプションを見ていきます。

タイマーの開始と停止

onStart() メソッドは、activity が表示される直前に呼び出されます。onStop() メソッドは、activity が表示されなくなった後に呼び出されます。これらのコールバックは、タイマーをいつ開始および停止するかについての適切な候補のようです。

  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 クラスと DessertTimer クラスの両方でフィルタリングされます。アプリが起動すると、タイマーもすぐに実行を開始することに注意してください。

  1. Back ボタンをクリックして、タイマーが再び停止することを確認します。Activity とそれが制御するタイマーの両方が破棄されたため、タイマーは停止します。

  2. 最近の画面を使用してアプリに戻ります。Logcat で、タイマーが 0 から再開することに注意してください。

  3. Share ボタンをクリックします。Logcat で、タイマーが実行中であることに注目してください。

  1. ホームボタンをクリックします。Logcat で、タイマーの実行が停止していることに注目してください。

  2. 最近の画面を使用してアプリに戻ります。Logcat で、タイマーが中断したところから再開することに注意してください。

  3. MainActivityonStop() メソッドで、stopTimer() の呼び出しをコメントアウトします。

  4. アプリを実行し、タイマーが開始したらホームボタンをクリックします。アプリがバックグランドにある場合でも、タイマーは実行されており、システムリソースを継続的に使用しています。タイマーを実行し続けることは、アプリのメモリリークであり、恐らく希望する動作ではありません。

一般的なパターンは、コールバックで何かを設定または開始すると、対応するコールバックでそれらを停止または削除する必要があります。これにより、不要になった時に何かを実行する必要がなくなります。

Note: 音楽の再生など、場合によっては、実行を続けたいことがあります。何かを実行し続けるための適切で効率的な方法がありますが、このコードラボの範囲外です。

  1. タイマーを停止する onStop() の行のコメントを外します。

  2. startTimer() の呼び出しを onCreate() に変更します。

  3. アプリを実行します。

  4. Home をクリックしてアプリを停止します。予想通り、タイマーは実行を停止します。

  5. 最近の画面を使用してアプリに戻ります。この時に、タイマーが再び開始されないことに注意してください。

覚えておくべき重要なポイント:

  • ライフサイクルコールバックでリソースを設定する時は、リソースも破棄します。
  • 対応する方法でセットアップと分解を行います。
  • onStart() で何かを設定した場合は、onStop() で停止するか、再度破棄します。

Android ライフサイクルライブラリを使用する

DessertClicker アプリでは、onStart() でタイマーを開始した場合、onStop() でタイマーを停止する必要があることを簡単に確認できます。タイマーは1つしかないので、タイマーを停止することを覚えておくのは難しくありません。

より複雑な Android アプリでは、onStart() または onCreate() で多くの設定を行い、onStop() または onDestroy() でそれらを全て破棄する場合があります。例えば、アニメーション、音楽、センサー、またはタイマーがあり、セットアップと破棄、および開始と停止の両方が必要な場合があります。忘れているとバグの種になってしまいます。

Android Jetpack の一部であるライフサイクルライブラリは、このタスクを簡素化します。このライブラリは、ライフサイクルの状態が異なるものもある多くの可動部品を追跡する必要がある場合に特に役立ちます。ライブラリはライフサイクルの動作方法を反転させます。通常、Activity または Fragment は、ライフサイクルコールバックが発生した時にコンポーネントに何をするかを指示します。ただし、ライフサイクルライブラリを使用すると、コンポーネント自体がライフサイクルの変更を監視し、それらの変更が発生した時に必要な処理を実行します。

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

  • コンポーネントはライフサイクルを所有します。ActivityFragment はライフサイクルの所有者です。ライフサイクルの所有者は、LifecycleOwner インターフェースを実装します。
  • ライフサイクル所有者の実際の状態を保持し、ライフサイクルの変更が発生した時にイベントをトリガーします。
  • LifecycleObserverはライフサイクルの状態を監視し、ライフサイクルが変更された時にタスクを実行します。LifecycleObserver は、LifecycleObserver インターフェースを実装します。

このタスクでは、DessertClicker アプリを Android ライフサイクルライブラリを使用するように変換し、ライブラリによって Android activity と fragment ライフサイクルの操作がどのように管理しやすくなるかを学習します。

DessertTimer を LifecycleObserver に変える

ライフサイクルライブラリの重要な部分は、lifecycle observer の概念です。監視により、クラスは Activity または Fragment のライフサイクルについて認識し、それらのライフサイクル状態の変化に応じて開始および停止できます。lifecycle observer を使用すると、activity と fragment のメソッドからオブジェクトを開始および停止する責任を取り除くことができます。

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

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

  • コンストラクターは、タイマーが監視しているライフサイクルである Lifecycle オブジェクトを受け取ります。
  • クラス定義は、LifecycleObserver インターフェースを実装します。

  • runnable 変数の下で、クラス定義に init ブロックを追加します。init ブロックで、addObserver() メソッドを使用して、所有者(activity) からこのクラス(observer)に渡されたライフサイクルオブジェクトを接続します。

 init {
   lifecycle.addObserver(this)
}
  1. startTimer()@OnLifecycleEvent アノテーションを付け、ON_START ライフサイクルイベントを使用します。lifecycle observer が監視できる全てのライフサイクルイベントは、 Lifecycle.Event クラスにあります。
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {}
  1. ON_STOP イベントを使用して、stopTimer() に対して同じことを行います。
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

MainActivity を変更する

FragmentActivity スーパークラスLifecycleOwner を実装しているため、MainActivity クラスは継承によってすでにライフサイクルの所有者になっています。したがって、Activity を lifecycle に対応させるために必要なことは何もありません。Activity のライフサイクルオブジェクトを DessertTimer コンストラクターに渡すだけです。

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

activity のライフサイクルプロパティは、この Activity が所有するライフサイクルオブジェクトを保持します。

  1. onCreate()onStop でのそれぞれのタイマー呼び出しメソッドを削除します。DessertTimer はライフサイクル自体を監視しており、ライフサイクルの状態が変化すると自動的に通知されるため、Activity で何をするかを DessertTimer に指示する必要はありません。これらのコールバックを行うのは、メッセージをログに記録することだけです。

  2. アプリをコンパイルして実行します。Logcat で期待通りログが表示されていることを確認してください。

  1. ホームボタンをクリックして、アプリをバックグランドに配置します。予想通り、タイマーの実行が停止していることに注意してください。

onSaveInstanceState() を使用して、アプリのシャットダウンをシュミレートする

Android がバックグランドでアプリをシャットダウンすると、アプリとそのデータはどうなりますか?これを理解することが重要です。

アプリがバックグランドに移行しても、破棄されることはなく、停止され、ユーザがアプリに戻るのを待つだけです。しかし、Android OS の主な懸念事項は1つは、フォアグランドでの activity をスムーズに実行し続けることです。たとえば、ユーザーがGPSアプリを使用してバスに乗るのを支援している場合、そのGPSアプリを素早くレンダリングし、道順を表示し続けることが重要です。ユーザが数日間見ていなかった可能性のある DessertClicker アプリを、バックグランドでスムーズに実行し続けることはそれほど重要ではありません。

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

Android は、アプリに関連する全ての Activity を含むアプリプロセス全体をシャットダウンすることもあります。Android は、システムにストレスがかかり、視覚的に遅れる危険がある場合にこの種のシャットダウンを実行するため、この時点で追加のコールバックやコードは実行されません。アプリのプロセスは、バックグランドでサイレントにシャットダウンされるだけです。しかし、ユーザには、アプリが閉じられているようには見えません。ユーザが AndroidOS にシャットダウンしたアプリに戻ると、Android はそのアプリを再起動します。

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

Note: 開始する前に、API28以降をサポートするエミュレーターまたはデバイスを実行していることを確認してください。

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

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

  1. アプリをコンパイルして実行します。カップケーキを数回クリックします。

  2. ホームボタンを押して、アプリをバックグランドに配置します。これでアプリは停止し、Android がアプリを使用しているリソースを必要とする場合、アプリは閉じられる可能性があります。

  3. Android Studio で、Terminal タブをクリックして、command-line terminal を開きます。

  1. adb と入力し、Return キーを押します。

Android Debug Bridge version X.XX.X で始まり、logcat で使用されるタグで終わる出力が多数表示される場合、全て問題ありません。adb: command not found が表示された場合は、実行パスで adb コマンドが使用可能であることを確認してください。手順については、Utilities chapter の "Add adb to your execution path" を参照してください。

  1. このコメントをコピーしてコマンドラインにに貼り付け、Return キーを押します。
adb shell am kill com.example.android.dessertclicker

このコマンドは、接続されているデバイスまたはエミュレータに、dessertclicker パッケージ名を使用してプロセスを停止するように指示しますが、アプリがバックグランドにある場合に限ります。アプリがバックグランドで実行されていたため、デバイスまたはエミュレーターの画面には、プロセスが停止したことを示すものは何も表示されません。Android Studio で、Run タブをクリックして、"Application terminated" というメッセージを表示します。Logcat タブをクリックして、onDestroy() コールバックが実行されなかったこと、つまり Activity が単に終了したことを確認します。

  1. 最近の画面を使用してアプリに戻ります。アプリは、バックグランドに配置されているか、完全に停止されているかに関係なく、最近の画面に表示されます。最近の画面を使用してアプリに戻ると、Activity が再開されます。Activity は、onCreate() を含むスタートアップライフサイクルコールバックのセット全体を追加します。

  2. アプリを再起動すると、"score"(販売されたデザートの数と合計金額の両方)がデフォルト値(0) にリセットされることに注目してください。Android がアプリをシャットダウンした場合、なぜそれが状態を保存しなかったのでしょう?

OS がアプリを再起動すると、Android はアプリを以前の状態にリセットしようとします。Android は、一部の View の状態を取得し、Activity から離れるたびにバンドルに保存します。自動的に保存されるデータの例としては、EditText のテキスト(レイアウトに ID が設定されている場合)や Activity の back stack があります。

ただし、Android OS が全てのデータを認識していない場合があります。たとえば、DessertClicker アプリに収益などのカスタム変数がある場合、Android Os はこのデータや Activity に対するその重要性を認識しません。このデータを自分でバンドルに追加する必要があります。

onSaveInstanceState() を使用して bundle data を保存します

onSaveInstance() メソッドは、Android OS がアプリを破棄した場合、必要になる可能性のあるデータを保存するために使用するコールバックです。lifecycle callback diagram では、Activity が停止した後に onSaveInstanceState() が呼び出されます。アプリがバックグランドに入るたびに呼び出されます。

onSaveInstanceState() 呼び出しを安全対策と考えてください。Activity がフォアグランドを終了する時に、少量の情報を bundle に保存する機会が与えられます。アプリをシャットダウンするまで待機した場合、OS がリソースの負荷にさらわれている可能性があるため、システムはこのデータを保存します。毎回データを保存することで、バンドルないの更新データを必要に応じて復元できるようになります。

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

   Timber.i("onSaveInstanceState Called")
}

Note: onSaveInstanceState() には2つのオーバーライドがあります。1つは outState パラメーターのみを使用し、もう1つは outState パラメーターと outPersistentState パラメーターを含みます。

  1. アプリをコンパイルして実行し、Home ボタンをクリックしてアプリをバックグランドに配置します。onSaveInstanceState() コールバックが onPause()onStop() の直後に発生することに注意してください。

  1. ファイルの先頭、クラス定義の直前に、次の定数を追加します:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

これらのキーは、instance state bundle からのデータの保存と取得の両方に使用します。

  1. onSaveInstanceState() まで下にスクロールし、Bundle タイプの outState パラメータに注目してください。

バンドルは、キーと値のペアコレクションであり、キーは常に文字列です。intboolean などのプリミティブ値をバンドルに入れることができます。 システムはこのバンドルを RAM に保存するため、バンドル内のデータを小さく保つことをお勧めします。サイズはデバイスごとに異なりますが、このバンドルのサイズも制限されています。通常は、100k 未満で保存する必要があり、そうしないと TransactionTooLargeException エラーでアプリがクラッシュするリスクがあります。

  1. onSaceInstanceState() で、putInt() メソッドを使用して revenue 値(integer) をバンドルに入れます。
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)

onCreate() を使用して bundle data を復元します

  1. onCreate() までスクロールし、メソッドのシグネチャを調べます:
override fun onCreate(savedInstanceState: Bundle) {...}

onCreate() は、呼び出されるたびにバンドルを取得することに注意してください。プロセスのシャットダウンが原因で Activity が再開されると、保存したバンドルが onCreate() に渡されます。Activity が開始された場合、onCreate() のこのバンドルは null です。したがって、Bundle が null でない場合は、以前にわかっていたポイントから Activity を "re-creating" していることが分かります。

Note: Activity が re-creating されている場合、onRestoreInstanceState() コールバックは、Bundle とともに onStart() の後に呼び出されます。ほとんどの場合、onCreate() で Activity の状態を復元します。ただし、onRestoreInstanceState() は、onStart() の後に呼び出されるため、onCreate() の呼び出し後に何らかの状態を復元する必要がある場合は、onRestoreInstanceState() を使用できます。

  1. DessertTimer のセットアップ後、次のコードを onCreate() に追加します。
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

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

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

  • キーとして機能する文字列。例えば、収益値の"key_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回押します。Home をクリックして、アプリをバックグランドに配置します。

  2. Android Studio Terminal タブで、adb を実行してアプリのプロセスをシャットダウンします。

adb shell am kill com.example.android.dessertclicker

最近の画面を使用してアプリに戻ります。今回は、アプリが正しい収益で返され、バンドルからのデザートの販売額に注目してください。しかし、デザートがカップケーキに戻ったことにも注目してください。アプリがシャットダウンから元の状態に戻るようにするために、もう1つやるべきことがあります。

  1. MainActivity で、showCurrentDessert() メソッドを調べます。このメソッドは、現在販売されているデザートの数と allDesserts 変数のデザートのリストに基づいて、Activity に表示するデザート画像を決定することに注意してください。
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

この方法は、販売されたデザートの数に依存して適切な画像を選択します。したがって、onSaveInstance() でバンドル内の画像への参照を保存するために何もする必要はありません。そのバンドルには、販売されたデザートの数がすでに保存されています。

  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 を使用してプロセスをシャットダウンします。最近の画面を使用してアプリに戻ります。ここで、全てのデータが正常に復元されたことに注目してください。

Configuration の変更を調べる

Activity と Fragment のライフサイクルの管理には、理解するべき重要な特別なケースがあります。それは、configuration の変更が Activity と Fragment のライフサイクルにどのように影響するかです。

configuration の変更は、デバイスの状態が急激に変化した時に発生するため、システムが変更を解決する最も簡単な方法は、Activity を完全にシャットダウンして再構築することです。たとえば、ユーザがデバイスの言語を変更した場合、様々なテキストの方向に対応するためにレイアウト全体を変更する必要がある場合があります。ユーザがデバイスをドックに接続したり、物理キーボードを追加したりする場合、アプリのレイアウトで異なるディスプレイサイズやレイアウトを利用する必要がある場合があります。また、デバイスの向きが変わった場合、新しい向きに合わせてレイアウトを変更する必要がある場合があります。

Device のローテーションとライフサイクルコールバックを調べる

  1. アプリをコンパイルして実行し、Logcat を開きます。

  2. rotation button でエミュレータを左右に回転させることができます。

  1. Logcat の出力を調べます。MainActivity で出力をフィルタリングします。

バイスまたはエミュレータが画面を回転させると、システムが全てのライフサイクルコールバックを呼び出して、Activity をシャットダウンすることに注意してください。次に、Activity が再作成されると、システムは全てのライフサイクルコールバックを呼び出して Activity を開始します。

  1. MainActivity で、onSaveInstanceState() メソッド全体をコメントアウトします。

  2. アプリをコンパイルして再度実行します。カップケーキを数回クリックし、デバイスまたはエミュレータを回転させます。今回は、デバイスを回転させて Activity をシャットダウンして再作成すると、 Activity はデフォルト値で起動します。

configuration の変更が発生すると、Android は、前のタスクで学習したのと同じインスタンス State バンドルを使用ステ、アプリの状態を保存および復元します。プロセスのシャットダウンと同様に、onSaveInstanceState() を使用してアプリのデータをバンドルに入れます。次に、デバイスが回転した場合に Activity state data が失われ内容に、onCreate() でデータを復元します。

  1. MainActivity で、onSaveInstanceState() メソッドのコメントを解除し、アプリを実行し、カップケーキをクリックして、アプリまたはデバイスを回転させます。今回は、デザートデータが Activity rotation 全体で保持されることに注目してください。

まとめ

Lifecycle tips

  • ライフサイクルコールバックで何かを設定または開始した場合は、対応するコールバックでそれらを停止または削除します。停止することで、不要になった時に実行を継続しないようにします。たとえば、onStart() でタイマーを設定した場合、onStop() でタイマーを一時停止または停止する必要があります。

  • onCreate() は、アプリが最初に起動した時に一回実行されるアプリの部分を初期化する場合にのみ使用してください。onStart() を使用して、アプリの起動時とアプリがフォアグランドに戻るたびの両方で実行されるアプリの部分を起動します。

Lifecycle library

  • Android ライフサイクルライブラリを使用して、ライフサイクル制御を Activity または Fragment から、ライフサイクル対応である必要がある実際のコンポーネントにシフトします。
  • Lifecycle owners は、Activity や Fragment などのライフサイクルを持つコンポーネントです。Lifecycle owners は、LifecycleOwner インターフェースを実装します。
  • Lifecycle observers は、現在の lifecycle state に注意をはらい、ライフサイクルが変更された時にタスクを実行します。Lifecycle observers は、LifecycleObserver インターフェースを実装します。
  • Lifecycle オブジェクトには実際の lifecycle state が含まれており、ライフサイクルが変更されるとイベントがトリガーされます。

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

  • ライフサイクルをオブザーブする必要があるクラスに LifecycleObserver インターフェースを実装します。
  • Activity または Fragment のライフサイクルオブジェクトを使用して、lifecycle observer クラスを初期化します。
  • lifecycle observer クラスで、ライフサイクル対応メソッドに、関心のあるライフサイクル状態の変化をアノテーションします。

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

プロセスのシャットダウンと Activity Sate の保存

  • Android は、フォアグランドアプリが問題なく実行できるように、バックグランドで実行されるアプリを規制します。この規制には、バックグランドでアプリが実行できる処理の量を制限することや、アプリプロセス全体をシャットダウンすることも含まれます。
  • ユーザは、システムがバックグランドでアプリをシャットダウンしたかどうかを知ることができません。アプリは引き続き最近の画面に表示され、ユーザがアプリを離れた時と同じ状態で再起動する必要があります。
  • Android Debug Bridge(adb) は、コンピューターに接続されているエミュレータやデバイスに指示を送信できるコマンドラインツールです。adb を使用して、アプリのプロセスシャットダウンをシュミレートできます。
  • Android がアプリプロセスをシャットダウンすると、onDestroy() ライフサイクルメソッドは呼ばれず、アプリは停止します。

Activity と Fragment の状態を保持

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

Configuration changes

  • Configuration の変更は、デバイスの状態が急激に変化した時に発生するため、システムが変更を解決する最も簡単な方法は、Activity をシャットダウンして再構築することです。
  • configuration 変更の最も一般的な例は、ユーザがデバイスを縦向きから横向きモードに、または横向きモードから縦向きモードに回転させる場合です。configuration の変更は、デバイスの言語が変更された時、またはハードウェアキーボードが接続された時にも発生する可能性があります。
  • configuration の変更が発生すると、Android は全ての Activity lifecycle のシャットダウンコールバックを呼び出します。次に、Android は Activity を最初から再開し、ライフサイクルの全ての起動コールバックを実行します。
  • configuration の変更が原因で Android がアプリをシャットダウンすると、onCreate() で使用できる state bundle を使用して Activity が再開されます。
  • プロセスのシャットダウンと同様に、アプリの状態を onSaveInstanceState() のバンドルに保存します。

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com