Activityコールバックメソッドの書き方と画面破棄時の保存方法(Bundleオブジェクト)【Androidアプリ開発/Kotlin】

Androidアプリ開発入門

Activityコールバッグメソッドの書き方

アクティビティのコールバックメソッドの書き方とBundleオブジェクトを使用した画面破棄時の処理方法を書いていきます。Jetpackを使用して構成する場合はあまり使わないと思いますが一応書いておきます。

タイトルを読んで何言ってるんですか?って思った人は下記の記事へどうぞ。ここのページで説明していることの概要を書いてます。

前回登場したライフサイクルの図を載せておきます。

Activityのライフサイクル

コールバックメソッドの書き方

どのコールバッグメソッドでも書き方は同じでメソッドをオーバーライドします。Kotlinはデフォルトでクラスやメソッドをオーバーライド出来ない様になっているため明示的にoverrideを宣言する必要があります。そして、superで親クラスのメソッドを呼び出しています。この記述は必ず必要なので下記がコールバックメソッドの最小構成になります。funは関数定義でfunctionの略。

override fun onStart() {
   super.onStart()
}

onCreate()の書き方

このコールバックは必ず実装する必要があります。Android Studioでは最初から書かれてるコードになります。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

関数定義

Kotlinの関数定義は下記の書式になります。

fun 関数名(引数: 型,引数: 型,…):戻り値型

型の後に付いてる[?]はKotlinではnull型には[?]を付ける必要があります。アプリ起動時のsavedInstanceStateはnull値のBundleになる為[?]が付いています。

fun onCreate(savedInstanceState: Bundle?) {}

親クラスの呼び出し

親クラスのonCreate()メソッドを呼び出してsavedInstanceStateパラメーターを渡しています。

super.onCreate(savedInstanceState)

レイアウトファイルの読み込み

殆どの場合、ここでUIを表示させるレイアウトファイルを読み込みます。setContentView()メソッドはレイアウトファイルをActivityにセットします。

//setContentView(R.layout.レイアウトファイル名)
setContentView(R.layout.activity_main)

Activityが破棄された時の処理(Bundleオブジェクト)

onSaveInstanceState()でインスタンスの状態を保存する処理を書いて、onCreate()で保存されたデータを参照します。Bundleオブジェクトでの保存方法で説明します。大きなデータを保存したい場合はViewModelを使った方が良いです。

onSaveInstanceState()

onSaveInstanceState()はonStopの後に呼ばれます。通常ここでActivityが破棄された場合のBundleオブジェクトを使用した保存処理を書きます。superで親クラスのonSaveInstanceState()を呼び出して保存しています。

    override fun onSaveInstanceState(outState: Bundle) {
        //Bundleオブジェクトで保存
        outState.putInt("CNT_KEY", cnt)
        outState.putString("MSG_KEY", textView2.text.toString())
        super.onSaveInstanceState(outState)
    }

上記のコードは下記の書き方でも大丈夫です。こちらの方が若干スッキリした書き方になってますね。

    override fun onSaveInstanceState(outState: Bundle) {
        outState?.run {
            putInt("CNT_KEY", cnt)
            putString("MSG_KEY", textView2.text.toString())
        }
        super.onSaveInstanceState(outState)
    }

Bundleオブジェクトの使い方

保存はBundleオブジェクトのput○○メソッドでKeyと値のセットで保存します。○○の部分には型の名前が入り、文字列ならputString()、数値ならputInt()、真偽値ならputBoolean()と言った形になります。keyはString型になるのでダブルクォテーション(“")で囲みます。Bundleオブジェクトは保存したい数だけ並べます。ただし同じKeyは使えないので被らない名前を使います。

//オブジェクト名.put○○(key名, 値)
outState.putInt("CNT_KEY", 100)
outState.putString("MSG_KEY", "message")
outState.putBoolean("FLG_KEY", true)

次にonCreate()で保存した値を取り出す処理を書きます。savedInstanceStateは最初の起動時はnullなのでnullかどうかで判定して処理します。onCreate()は起動時か破棄されて再作成された時しか呼ばれないので、値が入ってると言うことは破棄された状態になります。

破棄された時と通常時のsavedInstanceStateのフロー

保存したBundleオブジェクトを取得するにはget○○メソッドを使います。○○は型の名前が入ります。文字列ならgetString()、数値ならgetInt()、真偽値ならgetBoolean()と言った形になり、引数に保存した時のkeyを指定します。ビューに文字列などをいれる場合はレイアウトファイルの読み込みより前に書かない様にしてください。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //↑レイアウトファイルの読み込みより後に書く

        //破棄された場合の処理
        if(savedInstanceState !== null) {
            //Bundleオブジェクト.get○○(key名)
            var cnt = savedInstanceState.getInt("CNT_KEY")
            textView2.text = savedInstanceState.getString("MSG_KEY")
        }
    }

破棄された時に呼ばれるonRestoreInstanceState()

onRestoreInstanceState()は破棄された場合のみonStartの後に呼ばれます。

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        textView2.text = savedInstanceState?.getString("MSG_KEY")
    }

Activityコールバックメソッドの使い方のサンプルアプリ

アプリを起動するとタイマーが発動し、500になると停止します。アプリから離れるとカウントを中止して、再開するとその数値から再開します。この処理を入れない場合はバックグラウンドでもタイマーは進み続けます。また、画面破棄時は停止した数値からタイマーが再開します。onRestoreInstanceState()は使用していませんが、例として記載しています。

プロジェクト名ActivityCallbackSample
動作確認API29
最小APIAPI28
Kotlinバージョン1.3.72
Android Studioバージョン4.0.1
サンプルコード(Github)ActivityCallbackSample
Activityのコールバックを理解するタイマーサンプルアプリ

MainActivity.kt

package com.example.activitycallbacksample

import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    var cnt = 0
    val handler = Handler()

    //タイマー処理
    private val runnable = object : Runnable{
        override fun run() {
            cnt++
            textView1.text = cnt.toString()
            if(cnt < 500){
                handler.postDelayed(this,1000)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //破棄された場合の処理
        if(savedInstanceState !== null) {
            cnt = savedInstanceState.getInt("CNT_KEY")
            textView2.text = savedInstanceState.getString("MSG_KEY")
        }

        handler.post(runnable)
    }

    override fun onSaveInstanceState(outState: Bundle) {
        //Bundleオブジェクトで保存
        outState.putInt("CNT_KEY", cnt)
        outState.putString("MSG_KEY", textView2.text.toString())
        super.onSaveInstanceState(outState)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        //処理例
        //textView2.text = savedInstanceState?.getString("MSG_KEY")
    }

    override fun onPause() {
        super.onPause()
        textView2.text = getString(R.string.txt_stop)
        //タイマーを一時停止
        handler.removeCallbacks(runnable)
    }

    override fun onStart() {
        super.onStart()
        textView2.text = getString(R.string.txt_start)
        //タイマーを再開
        handler.post(runnable)
    }
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#DCFAB9"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="casual"
        android:text="@string/txt_count"
        android:textSize="100sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/textView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="casual"
        android:textColor="#F67E7E"
        android:textSize="36sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView1" />

</androidx.constraintlayout.widget.ConstraintLayout>

strings.xml

<resources>
    <string name="app_name">ActivityCallbackSample</string>
    <string name="txt_count">0</string>
    <string name="txt_start">Start counting!</string>
    <string name="txt_stop">Stop counting!</string>
</resources>