簡単にわかりたい人向け。
Androidではアクティビティが勝手にに終了されたりしてしまって状態が保存できないことがある。また、スマホを横にしたりしても、同様にアクティビティを再構築するというようなことをしているので、アクティビティがいちど終了する。
UI部品のクラスを継承したカスタムビューを作成してメンバ変数などを設定すると、上記のようなアクティビティが終了することによって状態を保存できなくなり、保存してあるデータが消失してしまうことなどがある。
例えば以下のカスタムビューでは、画像をクリックするたびに、その回数をカウントするような処理をしている。
アクティビティが破棄されるときにはイベントが通知されるので、そのときに現在の状況を保存するような処理が必要になる。
そのためにView用に状態保存をするための仕組みと、その状態を保存するためのインターフェースが用意されている。それが、Parcelableインターフェースを継承するView.BaseSaveStateインターフェースである。
以下が最小のクラスの形。難しくないので、詳しくはコメントを参照。
onSaveInstanceStateでは親のクラスからデータが保存されたParcelableクラスのデータを受け取ることができるので、それをImageViewDataのコンストラクタの引数で渡してImageViewDataのインスタンスを生成する。親クラスのParcelableのデータはImageViewDataの親クラスで、いろいろ上手いことやってくれる。そしてImageViewDataのプロパティにデータを保存すると、そのデータを保存してくれる。
OnRestoreInstanceStateでは保存したデータを得られるので、データを取得してMyImageViewのメンバ変数に数値を戻してあげる。
これでアクティビティが削除されて、このMyImageViewのインスタンスが破棄されても状態を維持することができる。
Androidではアクティビティが勝手にに終了されたりしてしまって状態が保存できないことがある。また、スマホを横にしたりしても、同様にアクティビティを再構築するというようなことをしているので、アクティビティがいちど終了する。
UI部品のクラスを継承したカスタムビューを作成してメンバ変数などを設定すると、上記のようなアクティビティが終了することによって状態を保存できなくなり、保存してあるデータが消失してしまうことなどがある。
例えば以下のカスタムビューでは、画像をクリックするたびに、その回数をカウントするような処理をしている。
class MyImageView : AppCompatImageView {
//画像をクリックした数を保存するメンバ変数 再生成されると=0で初期化される
private var mNum: Int = 0
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
//再生成されるのでここは必ず実行される
init {
//画像クリックで発生するイベントに対する処理
super.setOnClickListener {
//数値を+1する
mNum++
Log.v("nullpo", mNum.toString())
}
}
}
このような処理をした場合、例えばスマホを横にするとアクティビティはいったん破棄され、再生成されるため、MyImageViewも破棄され、再生成されるのでvar mNum: Int = 0がもういちど実行されてカウントしたクリック数がリセットされていまう。アクティビティが破棄されるときにはイベントが通知されるので、そのときに現在の状況を保存するような処理が必要になる。
そのためにView用に状態保存をするための仕組みと、その状態を保存するためのインターフェースが用意されている。それが、Parcelableインターフェースを継承するView.BaseSaveStateインターフェースである。
以下が最小のクラスの形。難しくないので、詳しくはコメントを参照。
class ImageViewData : View.BaseSavedState {
//状態を保存するために、変数をメンバ変数として記述する
//今回はnum一つだけだが、ここを任意で様々な変数を設定する
var num: Int = 0
//コンストラクタ ここはこういうものだと思おう
constructor(parcel: Parcel) : super(parcel) {}
constructor(superState: Parcelable?) : super(superState) {}
//これが必須のメソッド
override fun writeToParcel(out: Parcel?, flags: Int) {
super.writeToParcel(out, flags)
//記録したいデータを保存していく
out?.writeInt(num)
}
}
カスタムビュー側ではonSaveInstanceStateとonRestoreInstanceStateをoverrideして、さきほど作成したクラスを利用してデータを保存したり受け取ったりする。onSaveInstanceStateでは親のクラスからデータが保存されたParcelableクラスのデータを受け取ることができるので、それをImageViewDataのコンストラクタの引数で渡してImageViewDataのインスタンスを生成する。親クラスのParcelableのデータはImageViewDataの親クラスで、いろいろ上手いことやってくれる。そしてImageViewDataのプロパティにデータを保存すると、そのデータを保存してくれる。
OnRestoreInstanceStateでは保存したデータを得られるので、データを取得してMyImageViewのメンバ変数に数値を戻してあげる。
これでアクティビティが削除されて、このMyImageViewのインスタンスが破棄されても状態を維持することができる。
class MyImageView : AppCompatImageView {
//クリック回数を保存するメンバ変数
private var mNum: Int = 0
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
init {
super.setOnClickListener {
mNum++
Log.v("nullpo", mNum.toString())
}
}
//アクティビティが終了したりするときに呼ばれる
override fun onSaveInstanceState(): Parcelable {
//親クラスからデータを受け取り、さらに情報を追加して返値として返す
return ImageViewData(super.onSaveInstanceState()).also{
it.num = mNum
}
}
//保存した状態があるときに呼ばれる
override fun onRestoreInstanceState(state: Parcelable?) {
//保存された情報からデータを得てメンバ変数に設定する
if (state is ImageViewData) {
var data = state as ImageViewData
mNum = data.num
}
super.onRestoreInstanceState(state)
}
}