Activityのライフサイクル

投稿日:  更新日:

Activityはライフサイクルを持っています。ライフサイクルは「画面の作成~表示~破棄」という一連の流れのことです。

アプリケーションはこの流れに沿ってプログラミングします。流れに逆らったプログラムは作れません。

アプリケーションの開発者にとって、ライフサイクルの理解は最重要です。

Activityのライフサイクルについてまとめます。

スポンサーリンク

Activityライフサイクル

ライフサイクルとは

Activityはアプリケーションコンポーネント(アプリの部品)の1つで、主な役割は一枚の画面表示をすることです。

Activityはアンドロイドシステムにより制御される「画面の作成~表示~破棄」という一連の流れを持っています。そして、画面が切り替わる毎に繰り返されています。

「画面が切り替わる」とは、例えば次のような場面です。

 アプリを起動・終了・再開
 アプリ内で画面を移動(新規画面へ移ったり・元仮面へ戻ったり)
 他のアプリを起動、元のアプリへ復帰
 etc …

この「一連の流れ」のことを、生物の一生に見立てて「ライフサイクル」と呼びます。

ライフサイクルの状態遷移

アプリ内のActivityインスタンスはライフサイクルのさまざまな状態を遷移します。この状態遷移をActivityが認識できるように、コールバックが設けられています。

その様子を表したのが下図です。矢印上に遷移の条件を記載しています。

Activityライフサイクルの状態遷移

スポンサーリンク

ケース別の状態遷移

ライフサイクルの状態遷移が「どのような経路を通るか?」は、画面が切り替わる場面で異なります。

それを整理すると表のようになります。数字は遷移の順番です。

API<28API≧28
ケース
Activityの起動 
Activityの終了
startActivity( )で
 Sub Activityの起動(Bgへ)
 Sub Activityの終了(Fgへ)
startActivityForResult( )で
 Sub Activityの起動(Bgへ)
 Sub Activityの終了(Fgへ)
他のアプリ起動(Bgへ)
履歴(Recent)から復帰(Fgへ)
端末の回転
端末の電源Off
端末のSeeep
端末のWakeup
Activityの起動(SINGLE_TOP)
※Fg:フォアグラウンド、Bg:バックグラウンド
※API<28の場合、onSaveInstanceState ⇒ onStopの順番
ケース
Activityの起動 
Activityの終了
startActivity( )で
 Sub Activityの起動(Bgへ)
 Sub Activityの終了(Fgへ)
startActivityForResult( )で
 Sub Activityの起動(Bgへ)
 Sub Activityの終了(Fgへ)
他のアプリ起動(Bgへ)
履歴(Recent)から復帰(Fgへ)
端末の回転
端末の電源Off
端末のSeeep
端末のWakeup
Activityの起動(SINGLE_TOP)
※Fg:フォアグラウンド、Bg:バックグラウンド
※API≧28の場合、onStop ⇒ onSaveInstanceStateの順番

API≧28において、onSaveInstanceStateの順番がonStopと入れ替わっています。ドキュメントに次の説明があります。

If called, this method will occur after onStop() for applications targeting platforms starting with Build.VERSION_CODES.P. For applications targeting earlier platform versions this method will occur before onStop() …
——
onSaveInstanceStateが呼び出された場合、このメソッドはBuild.VERSION_CODES.P以降を対象とするアプリでonStopの後に発生します。以前を対象とするアプリケーションでは、このメソッドはonStopの前に発生し、…

スポンサーリンク

特殊な状態1:onActivityResult

新しく起動したActivity(図のSub)が終了する時に、結果を起動元のActivity(図のMain)へ返すことが出来ます。

Activityから結果を返す

その時、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}\"を受取りました!"
        }
    }
【注意】ライブラリandroidx.activity:activityが≧Ver1.2.0で非推奨
startActivityForResult( )ならびにonActivityResult( )は、ライブラリandroidx.activity:activity≧Ver1.2.0で非推奨になりました。代わりに「Activity Result API」の使用が推奨されています。
※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による起動の違い」を参照してください。

setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)

この時、インスタンスを配置しない代わりに、実行中のActivityインスタンスのActivity#onNewInstance( )が呼ばれます。

例えば次のような場合です。

            val _intent = Intent(this, Sub3Activity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
            }
            startActivity(_intent)
スポンサーリンク

関連記事:

起動されたActivityはAndroidの中で「スタックとタスク」という仕組みで管理されます。 LaunchModeはActivityを「スタックとタスク」の中へ配置する方法を決めます。 このLaunchModeがとても複雑で、アプリ開発で常に混乱を招く内容です。 なので、まとめました。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 アプリケーションコンポーネントの概要をまとめます。 ...
「インテントの解決」はシステム内でアプリケーションコンポーネントを選択する処理のことです。 この選択する処理の流れが複雑で、ちょっと癖がありあます。 ここでは「インテントの解決」の処理の流れについてまとめます。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 Activityはアプリケーションコンポーネントの1つです。アプリケーションの画面と処理を構築します。 Activityについてまとめます。 ...
スポンサーリンク