【Android】【Kotlin】Fragmentのライフサイクルに合わせて自動に参照を消してくれる仕組みを作る
IT技術
はじめに
Fragment で View の参照を持っている場合、onDestoryView() で参照を取り除いておくべきらしい。
恥ずかしながら、このことを最近知りました。
ViewBindingのガイドの例では
1 private var _binding: ResultProfileBinding? = null
2 // This property is only valid between onCreateView and
3 // onDestroyView.
4 private val binding get() = _binding!!
5
6 override fun onCreateView(
7 inflater: LayoutInflater,
8 container: ViewGroup?,
9 savedInstanceState: Bundle?
10 ): View? {
11 _binding = ResultProfileBinding.inflate(inflater, container, false)
12 val view = binding.root
13 return view
14 }
15
16 override fun onDestroyView() {
17 super.onDestroyView()
18 _binding = null
19 }
更に続けて
注: フラグメントはビューよりも持続します。フラグメントの
onDestroyView()
メソッドでバインディング クラスのインスタンスへの参照をすべてクリーンアップしてください。
と書かれています。
なるほど?
と思うと同時に、面倒くさいし実運用ではonDestoryView() での= null を忘れそうな気がしました。
とりあえず、DelegatedProperties を使えば解決できそうです。
車輪の再発明になるかもですが、自分で使いやすいように作っていこうと思います。
Fragmentのライフサイクルに合わせて自動に参照を消してくれる仕組みを作る
ライフサイクルに合わせて参照を消してくれる仕組みは、以前から個人的にKotterKnifeから一部拝借して使っていました。
https://gist.github.com/chrisbanes/fc4392dcbdc0aa5d99147dc551616676#file-kotterknife-kt-L123
ただ↑はReadOnlyProperty なので、var の変数では使えないです。これをベースにしてReadWriteProperty
で作り直すことにしました。
ライフサイクルと紐付けるためのLifecycleObserver が Deprecated になっているので代わりのDefaultLifecycleObserver を使います。
1private object EMPTY
2
3class LifecycleReadWriteProperty<T : LifecycleOwner, V> : ReadWriteProperty<T, V>, DefaultLifecycleObserver {
4 private var value: Any? = EMPTY
5 private var attachedToLifecycleOwner = false
6 private var thisRef: T? = null
7
8 override fun getValue(thisRef: T, property: KProperty<*>): V {
9 this.thisRef = thisRef
10 checkAddToLifecycleOwner(thisRef)
11 if (value == EMPTY) {
12 throw UninitializedPropertyAccessException()
13 }
14 @Suppress("UNCHECKED_CAST")
15 return value as V
16 }
17
18 private fun checkAddToLifecycleOwner(thisRef: T) {
19 if (!attachedToLifecycleOwner) {
20 when (thisRef) {
21 is Fragment -> thisRef.viewLifecycleOwner.lifecycle.addObserver(this)
22 else -> thisRef.lifecycle.addObserver(this)
23 }
24 attachedToLifecycleOwner = true
25 }
26 }
27
28 override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
29 checkAddToLifecycleOwner(thisRef)
30 this.value = value
31 }
32
33 override fun onDestroy(owner: LifecycleOwner) {
34 value = EMPTY
35 when (val thisRef = this.thisRef) {
36 is Fragment -> thisRef.viewLifecycleOwner.lifecycle.removeObserver(this)
37 else -> thisRef?.lifecycle?.removeObserver(this)
38 }
39 attachedToLifecycleOwner = false
40 this.thisRef = null
41 super.onDestroy(owner)
42 }
43}
これで、onDestory() がライフサイクルに合わせて呼び出されて、参照を消せるようになりました。
加えて↓
1inline fun <reified T : Fragment, V> bindViewLifecycle() = LifecycleReadWriteProperty<T, V>()
↑拡張関数を作成して Fragment で使いやすくすることで
公式 ViewBinding ガイドの例を以下のように書き換えることができます。
1 private val binding: ResultProfileBindnig by bindViewLifecycle()
2
3 override fun onCreateView(
4 inflater: LayoutInflater,
5 container: ViewGroup?,
6 savedInstanceState: Bundle?
7 ): View? {
8 binding = ResultProfileBinding.inflate(inflater, container, false)
9 val view = binding.root
10 return view
11 }
12}
だいぶスッキリしましたね。
実装中に心配になったonDestroyの呼び出しタイミング
DefaultLifecycleObserver のoverride fun onDestroy() の呼び出しタイミングってどこなんだろう?
ということでDefaultLifecycleObserver.onDestory() の呼び出しを遡ってみることにしました。
FullLifecycleObserverAdapter.onStateChanged でLifecycle.Event.ON_DESTROY のときに呼び出されているんですねー
そのまま更に辿ると、Fragment.performDestroyView() にてLifecycle.Event.ON_DESTROY を設定していることがわかりました。
1void performDestroyView() {
2 mChildFragmentManager.dispatchDestroyView();
3 if (mView != null && mViewLifecycleOwner.getLifecycle().getCurrentState()
4 .isAtLeast(Lifecycle.State.CREATED)) {
5 mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
6 }
7 mState = CREATED;
8 mCalled = false;
9 onDestroyView();
10 /* ...以下省略... */
11 }
よく読んでみると、直後にFragment.onDestroyView() を呼び出していますね。
つまり、
DefaultLifecycleObserverのoverride fun onDestroy() は、Fragment のonDestoryView() 直前で呼び出されている。
ということらしい。なるほど勉強になった。
おまけ
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
のmViewLifecycleOwner は、Fragment.getViewLifecycleOwner() で取得できる LifecycleOwner だ。
なるほど、やっぱりFragment.getLifecycle() で取得できるライフサイクルとは別物で、イベントのタイミングが微妙に違うんですねー
改めて、Fragement でライフサイクルと View を絡めるときはFragment.getViewLifecycleOwner() を使うべき、ということがなんとなくわかった。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ