アクティビティの状態とメモリからの退避
アクティビティ、システムのプロセスと強制終了の可能性については以下になっている。
プロセスの状態 | アクティビティの状態 | 強制終了の可能性 |
---|---|---|
フォアグラウンド (フォーカスがあるか、すぐにフォーカスを取得する状態) |
onCreate() onStart() onResume() |
低 |
バックグラウンド(フォーカスがない) | onPause() | 中 |
バックグラウンド(非表示) | onStop() | 高 |
空 | onDestroy() | 高 |
システムがメモリを開放するときはアクティビティ自体ではなく、アクティビティが実行されているプロセスを強制終了する。
ユーザーはアプリケーションマネージャーの「設定」からプロセスを強制終了してアプリを強制終了することができる。
プロセスとスレッドの詳細は こちら を参照。
一時的なUIの状態の保存と復元
アプリの構成変更(画面の回転やマルチウィンドウモードへの切り替え)が起こってもユーザーとしては同じ画面が表示されることを期待している。
しかし、実際はシステムによりアクティビティが破棄され新しいものが生成される場合がある。
ユーザーの期待に沿うためには ViewModel
、onSaveInstanceState()
、およびローカルストレージを組み合わせ状態を保存する必要がある。
インスタンスの状態
以下のケースでアクティビティが破棄される場合はコンセプトが完全に失われるが、ユーザーが期待している状態になるため特に考慮する必要がない。
- 「戻る」ボタンを押した場合
- アクティビティ内からfinish()メソッドを呼び出す機能を使った場合
しかし問題になるのはシステムの制約により破棄された場合(構成の変更やメモリ不足)で、その時のコンセプトは記憶される、アクティビティに復帰したときはその情報を使って元の状態を再現する。
その時に保存されるデータは インスタンス状態 と呼ばれ、Bundle
オブジェクトに格納されたキーとバリューのペアの集合になっている。
システムにはデフォルトで Bundle
オブジェクトに格納されたキーバリュー情報からViewの情報を復元する機能を持っているので特にコードは必要ない。(ただし各Viewには ID を設定する必要がある)
ただ、保存に使う Bundle
オブジェクトはメインスレッドでのシリアル化を必要とし多くのメモリを消費するため、ごく少量のデータを保存する以外には使うべきではない。(この場合は onSaveInstanceState()
と ViewModel
を組み合わせて使うようにする)
onSaveInstanceState() の使い方
アクティビティの停止が始まるとシステムは onSaveInstanceState()
メソッドを呼び出す。
デフォルト実装では、EditText
ウィジェット内のテキストや ListView
ウィジェットのスクロール位置など、アクティビティビュー階層に関わる情報が保存されるようになっている。
これに加えて他の情報を保存するためにはこのメソッドをオーバーライドし、Bundle
オブジェクトにキーバリューペアを保存する。もともとの上記の保存機能を使う場合はスーパークラスの同じメソッドを呼び出すこと。
override fun onSaveInstanceState(outState: Bundle?) { // Save the user's current game state outState?.run { putInt(STATE_SCORE, currentScore) putInt(STATE_LEVEL, currentLevel) } // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(outState) } companion object { val STATE_SCORE = "playerScore" val STATE_LEVEL = "playerLevel" }
注意!
ユーザーが「戻る」ボタンで明示的にアクティビティを閉じた場合や強制的にアプリを終了させた場合、または finish()
メソッドが呼び出された場合には onSaveInstanceState()
は呼ばれない。
保存済みのインスタンスの状態を使用してアクティビティのUIの状態を復元する
onCreate()
と onRestoreInstanceState()
で Bundle
を受け取り、状態の復元が可能。
onCreate()
の方は以前の情報がない場合の生成時も呼び出されるため、null 判定が必要になる。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Always call the superclass first // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { with(savedInstanceState) { // Restore value of members from saved state currentScore = getInt(STATE_SCORE) currentLevel = getInt(STATE_LEVEL) } } else { // Probably initialize members with default values for a new instance } // ... }
onRestoreInstanceState()
の場合は復元時にしか呼び出されないため、null判定は不要。ただし、ビュー階層の状態を復元できるようにスーパークラスの同メソッドを必ず呼び出すこと!
override fun onRestoreInstanceState(savedInstanceState: Bundle?) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState) // Restore state members from saved instance savedInstanceState?.run { currentScore = getInt(STATE_SCORE) currentLevel = getInt(STATE_LEVEL) } }