Android Kotlin基礎講座 04.2: 複雑なライフサイクル状態
目次
タスク:ライフサイクルの誤りを回避する
前回の記事では、ライフサイクルコールバックをオーバーライドし、コールバックが呼び出されたときにログを表示することで、アクティビティとフラグメントのライフサイクルを監視する方法を学習しました。このタスクではDessertClickerアプリにおけるライフサイクルタスクのより複雑な管理方法を学習します。秒数をカウントし続けながら、毎秒ログ文を表示するタイマーを使います。
ステップ1:DessertTimerのセットアップ
- 前回の記事で作成したDessertClickerアプリを開いてください。(アプリを作成していない場合はこちらからダウンロードできます)
- プロジェクトパネルのjava > com.example.android.dessertclickerを展開して、DessertTimer.ktを開いてください。全てのコードがコメントアウトされていることを確認してください。この状態ではアプリに全く影響を与えません。
- エディターウィンドウ内のコードを全て選択してください。その状態でCode > Comment with Line Commentを選択するか、Control+/(Macの場合Command+/)を押してください。このコマンドはファイル内のコードのコメントを全て解除するものです。(アプリをリビルドするまで未解決参照エラーが表示される場合があります)
- DessertTimerクラスがstartTime()とstopTime()を含んでいることを確認してください。これはタイマーをスタート・ストップするためのメソッドです。startTime()が動作している間、タイマーはログメッセージをトータルの経過時間と共に毎秒表示させます。stopTime()メソッドは逆にタイマーとログをストップさせます。
Note: DessertTimerクラスはタイマー用にRunnableクラスとHandlerクラスに紐づいたバックグラウンドスレッドを利用しています。この記事ではそれらについて知る必要はありません。(後の講座でスレッドについてはより深く学習します)
詳細を今すぐ知りたい方はプロセスとスレッドの概要をご覧ください。
- MainActivity.ktを開いてください。クラスの一番上、dessertsSold変数のすぐ下にタイマー用の変数を追加してください。
private lateinit var dessertTimer : DessertTimer;
- onCreate()までスクロールして、DessetTimerオブジェクトをsetOnClickListener()のすぐ下に作成してください。
dessertTimer = DessertTimer()
これでDessertTimerオブジェクトが出来上がりました。ここからはアクティビティが画面上に表示されているときのみタイマーを動作させるためにスタートとストップをどこで使うかについて考えていきます。次のステップではそのための手法をいくつか見ていきます。
ステップ2:タイマーをスタート・ストップする
onStart()メソッドはアクティビティが表示される直前に呼び出されます。onStop()メソッドはアクティビティが見えなくなった後に呼び出されます。これらのコールバックはタイマーのスタートおよびストップメソッドを設置するための有力な候補となり得るでしょう。
- MainActivityクラス中のonStart()コールバックの中でタイマーをスタートさせます。
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}
- onStop()の中でタイマーをストップさせます。
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}
- コンパイルしてアプリを起動してください。Android StudioでLogcatをクリックしてください。Logcatの検索フィールドにdessertclickerと入力して、MainActivityとDessertTimerクラスの両方をフィルターしてください。アプリが起動すると同時にタイマーもスタートすることを確認してください。
- 戻るボタンを押してタイマーが停止することを確認してください。アクティビティとアクティビティによってコントロールされていたタイマーが破棄されたのでタイマーは停止します。
- 最近開いた画面からアプリに戻ってください。Logcatのタイマーが0から再開していることを確認してください。
- 共有ボタンをタップしてください。Logcatでタイマーが動き続けていることを確認してください。
- ホームボタンをタップしてください。Logcatでタイマーが停止することを確認してください。
- 最近開いた画面からアプリに戻ってください。Logcatでタイマーが前回停止した秒数から再開されていることを確認してください。
- MainActivityのonStop()メソッドの中で、stopTime()の呼び出しをコメントアウトしてください。stopTime()をコメントアウトすることでonStart()で開始した処理をonStop()の中で停止し忘れた場合の状態を知ることができます。
- コンパイルしてアプリを起動してください。タイマースタートした後にホームボタンをタップしてください。アプリがバックグラウンドにあるにも関わらず、タイマーは動作し続けているので、システムリソースを消費し続けてしまいます。
タイマーを動作させ続けることはアプリのメモリの浪費ですし、本来期待する動作ではないはずです。
一般的なパターンとしては、コールバック中で何かをセットアップ、またはスタートした場合、それに対応するコールバックの中でそれらを停止、削除します。この方法によって、必要がないのに動作し続けているという状態を回避することができます。
Note: 音楽の再生など、いくつかのケースでは動作させ続けたい場合があります。そういったものを動作させ続けるための適切かつ効率的な方法もありますが、今回の講座の範囲外ですので、今回は説明を省きます。
- タイマーをストップさせるためのonStop()のコメントアウトを解除してください。
- onStart()からstartTime()の呼び出し部分を切り取りして、onCreate()に貼り付けてください。この変更では、何かをonCreate()で初期化し、onStart()でそれをスタートさせるのではなく、onCreate()の中で初期化・スタートの両方をする場合の挙動を確認できます。
- コンパイルしてアプリを起動してください。予期した通りにタイマーが動作することを確認してください。
- ホームボタンをおしてアプリを停止してください。予期した通りにタイマーが停止することを確認してください。
- 最近開いた画面からアプリに戻ってください。今回の場合、タイマーが再び動きだすことはありません。なぜならonCreate()はアプリが最初に起動するときに一回だけ呼び出されるからです。アプリがフォアグラウンドに戻った際には呼び出されません。
覚えておくべきキーポイント:
- ライフサイクルコールバックの中でリソースをセットアップする際には、破棄もする。
- 対応するコールバックメソッドの中でセットアップ・破棄をする。
- onStart()の中で何かをセットアップした場合、onStop()の中でそれを破棄する。