LoginSignup
2
3

More than 3 years have passed since last update.

【Kotlin】初心者向け: 画面回転やスリープ後のUIの保持

Last updated at Posted at 2020-06-30

はじめに

 今回は画面を回転させたときや本体をスリープさせた後、ビューの一部がその前後で変わってしまう問題について取り組みます。下の簡易的なボタンカウンターアプリでその様子が確認できます。縦画面でカウントアップさせたとき、横に切り替えると数字が0に戻ってしまうのです。
キャプチャ.PNG
キャプチャ2.PNG

原因

 この問題の原因は、画面の向きを変えるなどの諸動作によって、Activityがライフサイクルを一からやり直す事にあります。これによってハードウェアは、柔軟にかつ素早くユーザーのアクションを反映させることが出来ますが、同時にUIまでもが破棄されて初期化されてしまいます。これを防ぐためには、ライフサイクルが終わる前に、UI等の必要な情報を一時的に保持しておく必要があります。

*ライフサイクルについてはこちら:https://qiita.com/K4N4/items/2f4babe2bab67ddacf89

onSaveInstanceState

一時的に情報を保持する方法の一つに、onSaveInstanceStateを利用するというものがあります。この方法はあまり大容量ではない情報を、簡単に保持する時に利用します。使い方は非常にシンプルです。

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        outState?.putInt("key", i)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)

        i = savedInstanceState?.getInt("key", 0)
        textView.text = i.toString()
    }

 onSaveInstanceStateメソッド(データを保持する)とonRestoreInstanceStateメソッド(保持したデータを復元する)を呼び出し、put〇〇get〇〇で実際にデータの受け渡しを行っています。
 onSaveInstanceStateメソッドのoutState?.putInt("key", i)というのはoutState(データをメモリに保存する実体)に"key"(任意)というキーを使って、iというIntの値を保持する。という事を指しています。キーとはその名の通り鍵の事です。呼び出し側と共通のキーを持たなければ、データの受け渡しは出来ません。
 onRestoreInstanceStateメソッドのi = savedInstanceState?.getInt("key", 0)は、Activityが破棄されて値が初期化されてしまった i に、保持したデータの実態savedInstanceStateからgetIntメソッドで共通のキーを持つ値を取り出しています。getIntの第二引数は、putIntがnullの場合の値を設定します。
 この二つのメソッドとその中身を入れるだけで、簡単に設定した値の保持が行えます。

onSaveInstanceStateとonRestoreInstanceStateとライフサイクル

 先述したようにこの二つのメソッドを使えば、簡単にUIの保持を行えます。では、この二つのメソッドはActivityのライフサイクルにおいて、どの段階で機能してるのでしょうか?
android_jitsumu2_6.jpg
それを示すのがこの図です。この図はActivityの基本的な工程に、今回用いた二つのメソッド等を加えたものです。この図の通り、onSaveInstanceStateはonPauseの後に値の保持を、onRestoreInstanceStateonStartの後に値の復元を行っています。onRestoreInstanceStateの値はonCreateに影響を及ぼさなかったり、onSaveInstanceStateが呼び出されたら必ずアプリが止まることなどが分かります。

実際にこのライフサイクルを可視化してみる

class MainActivity : AppCompatActivity() {

    private var i : Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        Log.d(TAG, "onStart: called")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            i += 1
            textView.text = i.toString()
        }
    }

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

    override fun onResume() {
        Log.d(TAG, "onResume: called")
        super.onResume()
    }

    override fun onPause() {
        Log.d(TAG, "onPause: called")
        super.onPause()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        Log.d(TAG, "onSaveInstanceState: called")
        super.onSaveInstanceState(outState)

        outState.putInt("key", i)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        Log.d(TAG, "onRestoreInstanceState: called")
        super.onRestoreInstanceState(savedInstanceState)

        i = savedInstanceState.getInt("key", 0)
        textView.text = i.toString()
    }

    override fun onStop() {
        Log.d(TAG, "onStop: called")
        super.onStop()
    }

    override fun onRestart() {
        Log.d(TAG, "onRestart: called")
        super.onRestart()
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy: called")
        super.onDestroy()
    }
}

MainActivityの中身をこのようにすることで、実際にどこでデータの保持等が行われているかログを通して可視化することが出来ます。このソースコードでは、上で示したカウントアップ機能のためにデータのやり取りを行っています。上手くいけば、各工程ごとにログに出力がなされます。是非回転などさせて試してみてください。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3