Activityはライフサイクルを持っています。ライフサイクルは「画面の作成~表示~破棄」という一連の流れのことです。
アプリケーションはこの流れに沿ってプログラミングします。流れに逆らったプログラムは作れません。
アプリケーションの開発者にとって、ライフサイクルの理解は最重要です。
Activityのライフサイクルについてまとめます。
目次
Activityライフサイクル
ライフサイクルとは
Activityはアプリケーションコンポーネント(アプリの部品)の1つで、主な役割は一枚の画面表示をすることです。
Activityはアンドロイドシステムにより制御される「画面の作成~表示~破棄」という一連の流れを持っています。そして、画面が切り替わる毎に繰り返されています。
「画面が切り替わる」とは、例えば次のような場面です。
アプリを起動・終了・再開
アプリ内で画面を移動(新規画面へ移ったり・元仮面へ戻ったり)
他のアプリを起動、元のアプリへ復帰
etc …
この「一連の流れ」のことを、生物の一生に見立てて「ライフサイクル」と呼びます。
ライフサイクルの状態遷移
アプリ内のActivityインスタンスはライフサイクルのさまざまな状態を遷移します。この状態遷移をActivityが認識できるように、コールバックが設けられています。
その様子を表したのが下図です。矢印上に遷移の条件を記載しています。
ケース別の状態遷移
ライフサイクルの状態遷移が「どのような経路を通るか?」は、画面が切り替わる場面で異なります。
それを整理すると表のようになります。数字は遷移の順番です。
ケース | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Activityの起動 | 1 | 2 | 3 | |||||||||
Activityの終了 | 1 | 2 | 3 | |||||||||
startActivity( )で Sub Activityの起動(Bgへ) | 1 | 2 | 3 | |||||||||
Sub Activityの終了(Fgへ) | 1 | 2 | 3 | |||||||||
startActivityForResult( )で Sub Activityの起動(Bgへ) | 1 | 2 | 3 | |||||||||
Sub Activityの終了(Fgへ) | 1 | 2 | 3 | 4 | ||||||||
他のアプリ起動(Bgへ) | 1 | 2 | 3 | |||||||||
履歴(Recent)から復帰(Fgへ) | 1 | 2 | 3 | |||||||||
端末の回転 | 5 | 6 | 7 | 8 | 1 | 2 | 3 | 4 | ||||
端末の電源Off | 1 | 2 | 3 | |||||||||
端末のSeeep | 1 | 2 | 3 | |||||||||
端末のWakeup | 1 | 2 | 3 | |||||||||
Activityの起動(SINGLE_TOP) | 2 | 3 | 1 | |||||||||
※Fg:フォアグラウンド、Bg:バックグラウンド ※API<28の場合、onSaveInstanceState ⇒ onStopの順番 |
ケース | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Activityの起動 | 1 | 2 | 3 | |||||||||
Activityの終了 | 1 | 2 | 3 | |||||||||
startActivity( )で Sub Activityの起動(Bgへ) | 1 | 3 | 2 | |||||||||
Sub Activityの終了(Fgへ) | 1 | 2 | 3 | |||||||||
startActivityForResult( )で Sub Activityの起動(Bgへ) | 1 | 3 | 2 | |||||||||
Sub Activityの終了(Fgへ) | 1 | 2 | 3 | 4 | ||||||||
他のアプリ起動(Bgへ) | 1 | 3 | 2 | |||||||||
履歴(Recent)から復帰(Fgへ) | 1 | 2 | 3 | |||||||||
端末の回転 | 5 | 6 | 7 | 8 | 1 | 3 | 2 | 4 | ||||
端末の電源Off | 1 | 3 | 2 | |||||||||
端末のSeeep | 1 | 3 | 2 | |||||||||
端末のWakeup | 1 | 2 | 3 | |||||||||
Activityの起動(SINGLE_TOP) | 2 | 3 | 1 | |||||||||
※Fg:フォアグラウンド、Bg:バックグラウンド ※API≧28の場合、onStop ⇒ onSaveInstanceStateの順番 |
API≧28において、onSaveInstanceStateの順番がonStopと入れ替わっています。ドキュメントに次の説明があります。
——
onSaveInstanceStateが呼び出された場合、このメソッドはBuild.VERSION_CODES.P以降を対象とするアプリでonStopの後に発生します。以前を対象とするアプリケーションでは、このメソッドはonStopの前に発生し、…
特殊な状態1:onActivityResult
新しく起動したActivity(図のSub)が終了する時に、結果を起動元のActivity(図のMain)へ返すことが出来ます。
その時、Activityの起動はActivity#startActivityForResult( )を用います。Activityの結果はActivity#onActivityResult( )コールバックの引数に返ります。
companion object { const val REQUEST_SUB = 111 const val REQUEST_KEY = "RequestKey" } ... override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<Button>(R.id.btnSubReturn).setOnClickListener { val _intent = Intent(this, SubActivity::class.java).apply { putExtra(REQUEST_KEY, "1234") } startActivityForResult(_intent, REQUEST_SUB) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when(requestCode) { REQUEST_SUB -> { // SubActivityからの結果 if(resultCode == RESULT_OK) data?.getStringExtra(SubActivity.RESULT_KEY)?.let { txtMesg.text = "Subから\"${it}\"を受取りました!" } else txtMesg.text = "Subの処理はキャンセルされました!" } else -> {} } }
companion object { const val RESULT_KEY = "ResultKey" } ... override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<Button>(R.id.btnCancelSub).setOnClickListener { setResult(RESULT_CANCELED, intent) // 結果を設定 finish() } findViewById<Button>(R.id.btnFinishSub).setOnClickListener { intent.putExtra(RESULT_KEY, "ABCDE") setResult(RESULT_OK, intent) // 結果を設定 finish() } intent.getStringExtra(MainActivity.REQUEST_KEY)?.let { txtMesg.text = "Mainから\"${it}\"を受取りました!" } }
※startActivity( )は非推奨になっていません。
特殊な状態2:onSaveInstanceState
端末の回転が行われる時、表示中のActivityは終了され、端末の向きに合わせた画面を構築するためにActivityが再起動されます。
これが原因で、画面の表示内容やユーザの操作内容が再起動されたActivith側へ反映されないという問題が起こります。
onRestoreInstanceStateで復元
この問題を回避するために端末の回転では、Activity#onSaveInstanceState( )で表示内容や操作内容を保存し、Activity#onRestoreInstanceState( )で復元できます。
保存・復元は引数savedInstatnceState(Bundle型)を介して行うようになっています。
class MainActivity : AppCompatActivity() { companion object { const val COUNT_KEY = "CountKey" } private var count = 0 private lateinit var txtMesg: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) txtMesg = findViewById(R.id.txtMesg) findViewById<Button>(R.id.btnPush).setOnClickListener { txtMesg.text = makeMesg(++count) } } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) count = savedInstanceState.getInt(COUNT_KEY) // 復元 txtMesg.text = makeMesg(count) } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt(COUNT_KEY, count) // 保存 } fun makeMesg(count: Int) = if(count > 0) "ボタンは${count}回押されました!" else "ボタンは押されていません!" }
onCreateで復元
復元側はActivity#onRestoreInstanceState( )の代わりにActivity#onCreate( )を使って同様なことができます。ただし、Activityの初回起動(アプリの起動)では、savedInstanceStateがnullになるため、対応が必要です。
class MainActivity : AppCompatActivity() { companion object { const val COUNT_KEY = "CountKey" } private var count = 0 private lateinit var txtMesg: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) txtMesg = findViewById(R.id.txtMesg) // savedInstanceState == null:Activity新規作成、アプリ起動 // savedInstanceState != null:Activity再作成、 端末回転 count = savedInstanceState?.let{ it.getInt(COUNT_KEY) }?:0 // 復元 txtMesg.text = makeMesg(count) findViewById<Button>(R.id.btnPush).setOnClickListener { txtMesg.text = makeMesg(++count) } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt(COUNT_KEY, count) // 保存 } fun makeMesg(count: Int) = if(count > 0) "ボタンは${count}回押されました!" else "ボタンは押されていません!" }
特殊な状態3:onNewInstance
実行中のActivityからActivity#startActivity( )を使って新たにActivityの起動をする時、Intent#flagsにIntent.FLAG_ACTIVITY_SINGLE_TOPが指定されていると、インスタンスをスタックへ重複して配置しない動作になります。
詳細は「ActivityのLaunchModeによる起動の違い」を参照してください。
この時、インスタンスを配置しない代わりに、実行中のActivityインスタンスのActivity#onNewInstance( )が呼ばれます。
例えば次のような場合です。
val _intent = Intent(this, Sub3Activity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(_intent)
関連記事: