カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit3 – 1 第4回 前編:ライフサイクルとコールバック

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit3 – ナビゲーションUnit3-1 第4回 前編 の内容をまとめたものです(翻訳ではありません)


Unit3 – 1 第4回

第4回はアクティビティのライフサイクルについてのチュートリアルです

  • Androidのアクティビティ(activity)についてです
  • アクティビティ・ライフサイクル(activity lifecycle)とアクティビティのそれぞれの状態(state)について、最初の初期化から最後の破棄まで見ていきます
  • アクティビティ・ライフサイクルはアクティビティの一連の状態のことです
  • アクティビティの状態(state)には、Initialized , Created , Started , Resumed , Destroyed の 5 つのがあります
  • ライフサイクルは、アクティビティが最初に作られたときから破棄されてアクティビティのリソースをシステムが回収するまでずっと続きます
  • アクティビティを行ったり来たりすると、アクティビティのライフサイクルの状態もそれに合わせて変化します
  • アクティビティがライフサイクルの変化に正しく応答しないと、おかしな振る舞いをしたり、Androidシステムのリソースを無駄に消費したりしてしまいます

最初に次のような全体的なはなしをしています

  • Logcat にログ情報を表示してみます
  • アクティビティのライフサイクルについて説明します
  • アクティビティの状態が変化するときに呼び出されるコールバック(callback)について説明します
  • ライフサイクルのコールバック(メソッド)をオーバーライドする方法について説明します

前編のレッスンはこんなかんじですね

  • DessertClicker というスタータアプリが用意されています。これを修正して Logcat に情報を表示してみます
  • ライフサイクルのコールバックをオーバーライドして、アクティビティの状態の変化をログに出力するコードを書いてみます

中編はいくつかの操作パターンでライフサイクルの変化を見ていきます

  • アプリを実行して、アクティビティが Start, Stop, Resume する様子を Logcat に書き出してみます

後編ではアプリの状態を復元してみます

  • デバイスの状態が変わることで消えてしまう恐れのあるアプリデータを保持しておくのに onSaveInstanceState() メソッドを実装してみます
  • アプリが自動で再起動したときにデータを復元(restore)するコードを書いてみます

ライフサイクルとコールバック

このチュートリアルはスターターアプリを使いながら進めると分かりやすいです

  • ダウンロードして使える DessertClicker アプリをスターターアプリとして使います
  • アプリの画面に表示されているデザートをタップすると、デザートが購入されます。購入されたデザートの数と合計金額が画面に表示されます
  • このアプリには、Androidのライフサイクルに関係するバグがいくつかあり、そのままになっています
    • たとえばある状況では、アプリはデザートの数や合計金額を0にリセットしてしまいます
  • Androidのライフサイクルを理解すると、どうしてこのようなことが起こるのか、そしてどうやったら修正できるのかが分かります

スターターアプリのセットアップ

ダウンロードしてインポートしてくださいね

  1. まず こちら にアクセスして「Code > Download ZIP」でZIPファイルをPCにダウンロードし、展開します
  2. Android Studio を起動して、新規にプロジェクトをインポートします
  3. インポートが完了したら Run でアプリをビルドして実行します。ちゃんと動作するか確認しておきます
  4. Project を眺めると、アプリにどういうファイルが実装されているのか確認できます

ライフサイクルとは

  • アクティビティにはライフサイクルと呼ばれるものがあります
  • アクティビティのライフサイクルは、最初に初期化されるところから始まって、最後に破棄されてシステムによってメモリが回収されるまでの一連の状態のことをいいます
  • ユーザがアプリを起動したとき、アクティビティ間を移動したとき、アプリに入ったり出たりしたとき、アクティビティはその状態(state)を変更します
  • アクティビティのライフサイクルは次のように状態(state)を変化させます
    • 初期化(Initialized) → Created → 破棄(Destroyed)
    • Created ⇔ Started(画面表示) ⇔ Resumed(操作可能)

うん、なんとなくわかりそう

コールバックとは

処理はここに記述するの

  • Androidはアクティビティがある状態からほかの状態に移るときコールバック(callback)と呼ばれる関数を呼び出します
  • コールバックを使って、アクティビティのライフサイクルの状態が変わったときに何らかのコードを実行できます
  • Activity クラスやそのサブクラスであるたとえば AppCompatActivity クラスなどには、ライフサイクルのコールバックメソッドがまとめて実装されています
  • コールバックには、onCreate(), onDestroy, onStart, onStop, on Resume, onPause などいろいろなものがあります
  • これらのコールバックはアクティビティでオーバーライドして利用します
  • ライフサイクルの状態(state)とコールバック(callback)の呼び出しは次のように移り変わります
                onRestart →
 [Initialized] → onCreate → [Created] → onStart → [Started] → onResume → [Resumed]
 [Destroyed]   ←                       ← onStop  ←           ← onPause  ←
   メモリ上                  オブジェクト              画面表示                 操作可能
  • アクティビティが見えているのは Started と Resumed のときです
  • アクティビティが操作可能な(フォーカスがある)のは Resumed のときです

ログ機能を使ってライフサイクルの状態を確認する

ログ機能を使うと、コードがちゃんと動いているのか確認したりできるの

  • Android Studio のログ機能を使って、ライフサイクルのなかでコールバックがどのタイミングで呼ばれているのかを調べてみます
  • ログ機能を使うと、アプリを動作させながらコンソール(Logcat)に短いメッセージを書き出していくことができます

onCreate() にログ出力を追加する

Dessert Clickerアプリを起動して、デザートの画像を何度かタップしてみます。Desserts Sold(デザートの売り上げ個数)とその下の総額の値がどんなふうに変わっていくのか見ていきます

  • MainActivity.kt を開きます
  • onCreate() メソッドのところに移動します
override fun onCreate(savedInstanceState: Bundle?) {
...
}

onCreate は見慣れてきたかも

  • onCreate() はこれまでのチュートリアルでも出てきています
  • onCreate() はどんなアクティビティにも必ず実装されなければなりません
  • onCreate() メソッドはアクティビティに対して1度だけ呼ばれて設定の初期化を行います
    • レイアウトの生成、クリックリスナーの定義、view binding の設定などです
  • 新しいアクティビティオブジェクトがメモリの中に作られると、そのすぐあとに onCreate() が呼ばれます
  • onCreate()が実行された後、アクティビティは Created の状態になります

onCreate() メソッドをオーバーライドするとき、アクティビティの生成を完了するにはスーパークラスの実装を呼び出す必要があります。メソッドの中で super.onCreate() を呼び出します(他のコールバックメソッドでも同様です)

Log.d() を使ってみる

ログの出力は Log クラスを使うだけなの

  • onCreate() メソッドのところに移動して、super.onCreate() の後ろに次の行を追加します
Log.d("MainActivity", "onCreate Called")
  • 自動インポートを有効にしていない場合は、次のようにしてLog クラスをインポートする必要があります
import android.util.Log
  • Log クラスは Logcat にメッセージを書き出します
    • Logcat はログメッセージのためのコンソールです
    • アプリに関する Android からのメッセージはここに表示されます
    • Log.d() を使って明示的にログに送られたメッセージはもちろん、ほかの Log クラスのメソッドからのメッセージなどもここに表示されます
  • ここで使った Log.d() メソッドはデバッグメッセージを書き出すものです
  • Log クラスにあるメッセージには、他にも次のようなものがあります
    • Log.i()・・・情報を提供するメッセージ
    • Log.e()・・・エラーメッセージ
    • Log.w()・・・警告メッセージ
    • Log.v()・・・その他いろいろなメッセージ
  • ログ出力で指定される最初のパラメータをタグ(tag)といいます。上の例では “MainActivity” と指定されています
  • タグ(tag)は文字列で、Logcat でメッセージを見つけるのに役立ちます
  • タグ(tag)は通常はクラス名にしておきます

タグはクラス名にしちゃえばいいんですね

  • 2番目のパラメータがメッセージ本体で、短い文字列で指定します。今回は “onCreate called” となっています
  • クラスの中で定数 TAG を宣言しておくことがよく行われます

const val TAG = "MainActivity"

  • 定数 TAG を宣言した場合、ログ出力はこんな風に記述します

Log.d(TAG, "onCreate Called")

いつでも TAG って書けちゃっていいかも

コンパイル時定数(const)について

  • コンパイル時定数( compile-time constant )は、変更されることのない値です
  • コンパイル時定数にするには、変数の宣言の前に const をつけます

アプリを実行してみる

とりあえず実行して Logcat を見てみましょうね

  • アプリをビルドして DessertClicker アプリを実行してみます
  • アプリをタップしてみます。今までとなんら違いは見られないはずです
  • Android Studio で画面の下部にある Logcat タブをクリックしてみます
  • Logcat に表示される大量のメッセージのほとんどはとりあえず使いません
  • Logcat ウィンドウで、検索のところに D/MainActivity と入れてみます
  • 検索フィールドで D/ をつけると、検索は Log.d() で作られたデバッグメッセージを対象に行われます

表示が絞られて見やすいです

  • ログ出力のタグ(tag)ににクラス名 “MainActivity” を付けているので、onCreate() で記述したログからの出力を抽出できます
  • Logcat に出力されるログメッセージには、日付と時間、パッケージ名、ログのタグ、メッセージ、が出力されます
  • onCreate() に Log.d() で記述したメッセージが出力されていることから onCreate() が実行されたことが分かります

onStart() メソッドを実装する

Started の状態になる直前に呼ばれるの

  • onStart() は、onCreate() のあとに呼ばれます
  • onStart() が実行された後、アクティビティーは画面に表示されます
  • アクティビティを初期化するのに一度だけ呼ばれるonCreate() と違って、onStart() はアクティビティのライフサイクルの中で何度でも呼び出すことができます
  • onStart() は onStop() に対応しており、これらは対になっています
  • ユーザがアプリを起動したあとにデバイスのホーム画面に戻った場合、アクティビティは停止(stop)して画面に表示されない状態になります

onStart() のひな型を追加する

Android Studio なら半自動なんです

  • Android Studio で MainActivity.kt を開きます
  • MainActivity クラスの中でメソッドを挿入したい場所にカーソルを移動させます
  • Ctrl+o(または Code > Override Methods を選択)します
  • このクラスでオーバーライドできるすべてのメソッドがリストになってダイアログに現れます
  • キーボードから onStart と入力すると onStart を含む部分が反転表示されます。下向き矢印を押すと一致するものに移動します
  • リストから onStart() を選んで OK をクリックするとオーバーライドするコードのひな型が挿入されます
  • こんな感じのコードが挿入されます
override fun onStart() {
   super.onStart()
}

タグに使う定数を確認する

  • onCreate() のログ出力のときに設定した定数 TAG を onStart() でもそのまま使用できます
  • MainActivity.kt ファイルのトップレベルに定数 TAG が追加されていることを確認します
    • 記述する場所は MainActivity クラスの外、import 宣言との間あたりです
    • const はトップレベル、あるいは object の中で使います
    • タグは通常クラス名にしておきます
  • こんな風に記述されているはずです
const val TAG = "MainActivity"

うん、ちゃんと追加されてます

ログ出力を追加する

onStart() メソッドの中に、ログメッセージを追加します

ログ出力は Log.d() でどうぞ

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

アプリを実行してログを確認する

Logcat を見ていてくださいね

  • DessertClickerアプリをビルドして実行します
  • Logcat を開いて、ログをフィルターするために検索フィールドに D/MainActivity と入力します
  • onCreate() と onStart() が順に呼ばれているのがわかります
  • Androidデバイスのホームボタンを押してホーム画面に移動してから、もう一度アプリのアクティビティに戻ってみます
  • アクティビティは切り替わった時点に戻り、すべて同じ値を持ち、そして 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
  • アプリをあれこれ操作してライフサイクルのコールバックを観察していると、デバイスを回転させたときに通常出ない振る舞いをするのに気づくかもしれません(onCreate が呼び出されたりする)
  • これについてはこのチュートリアルの後編で説明します

他のコールバックのログも追加する

onResume, onPause, onStop, onDestroy, onRestart も同じように追加するの

  • 他のライフサイクルメソッド(コールバック)についてもログを実装してみます
  • 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")
}

アプリを実行してログを確認する

  • 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つのライフサイクルコールバックが順に呼ばれることがわかります
    1. onCreate()・・・アクティビティを生成する
    2. onStart()・・・アクティビティを画面に表示する
    3. onResume()・・・アクティビティにフォーカスを移してユーザが操作できるようにする
  • Resume(再開)という名前がついているのでちょっと分かりにくいんですが、onResume() メソッドは Resume(再開)するものがない起動時にも呼ばれます

まとめ

ライフサイクルの基本はこれで大丈夫

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

アクティビティはライフサイクルとコールバックで考えるんですね

  • アクティビティ・ライフサイクル(activity lifecycle)は、アクティビティがとりうる一連の状態のことです
  • アクティビティ・ライフサイクルは、アクティビティが最初に生成(Created)されたときにはじまり、アクティビティが破棄(Destroyed)されたときに終わります
  • ユーザがアクティビティの間を移動したり、アプリに入ったり出たりするとき、それぞれのアクティビティはアクティビティ・ライフサイクルの中の状態を変化させます
  • アクティビティ・ライフサイクルの中のそれぞれの状態には対応するコールバックメソッド(ライフサイクルメソッド)があります。アクティビティのクラスの中でオーバーライドして利用します
  • 核になるコールバックメソッド(ライフサイクルメソッド)は onCreate(), onStart(), onPause(), onRestart(), onResume(), onStop(), onDestroy() の7つです
  • アクティビティが他のライフサイクルの状態に移るときに実行したい処理があるときは、その状態に対応するコールバックメソッドをオーバーライドします
  • Android Studio を使ってオーバーライドしたいメソッドのひな型を追加するには、追加したい場所にカーソルを置いて Ctrl+o(または Code > Override Methods を選択)を押します

Log を使って記録の出力(ロギング)をする

アプリの動作確認にログをどんどん使ってくださいね

  • Android のログAPI(具体的には Log クラス)を使うと、Android Studio の Logcat に短いメッセージを書き出せます
  • デバッグメッセージには Log.d() を使います
    • このメソッドは2つの引数をとります
    • ひとつはログのタグです(一般的にはクラス名を指定します)
    • もうひとつはログのメッセージで、短い文字列です
  • 出力されたログメッセージは Android Studio の Logcat ウィンドウに表示されます