postDelayedが危険な理由

多くの場合、androidシステムとsdkの特性により、システムの特定の部分が構成されるか、必要なイベントが発生するまで待つ必要があります。これはしばしばクラッチですが、特に締め切りに直面して、それなしではできない場合があります。したがって、多くのプロジェクトがこれにpostDelayedを使用しています。カットの下で、彼がなぜそんなに危険なのか、そしてそれに対して何をすべきかを考えます。



問題



まず、postDelayed()が一般的にどのように使用されるかを見てみましょう。



override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.postDelayed({
            Log.d("test", "postDelayed")
            // do action
        }, 100)
}


見た目は良いですが、このコードを詳しく見てみましょう。



1)これは延期されたアクションであり、完了するまでしばらく待ちます。ユーザーが画面間をどの程度動的に遷移できるかを知っているので、フラグメントを変更するときはこのアクションをキャンセルする必要があります。ただし、これはここでは発生せず、現在のフラグメントが破棄された場合でもアクションが実行されます。



確認は簡単です。2つのフラグメントを作成し、2番目に切り替えると、postDelayedを長い時間(たとえば5000ミリ秒)で実行します。すぐに戻ります。そしてしばらくすると、アクションがキャンセルされていないことがログに表示されます。



2)2番目は最初から「フォロー」します。このランナブルでフラグメントのプロパティへの参照を渡すと、ランナブルへの参照がフラグメント自体よりも長く存続するため、メモリリークが発生します。



3) :

, view onDestroyView

synthitec - java.lang.NullPointerException, _$_clearFindViewByIdCache, findViewById null

viewBinding - java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null



?



  1. view — doOnLayout doOnNextLayout
  2. , - (Presenter/ViewModel - ). .
  3. .


, view window.



    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
         Runnable {
            // do action
        }.let { runnable ->
            view.postDelayed(runnable, 100)
            view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
                override fun onViewAttachedToWindow(view: View) {}

                override fun onViewDetachedFromWindow(view: View) {
                    view.removeOnAttachStateChangeListener(this)
                    view.removeCallbacks(runnable)
                }
            })
        }
    }


doOnDetach , view window, onViewCreated. .



View.kt:



inline fun View.doOnDetach(crossinline action: (view: View) -> Unit) {
    if (!ViewCompat.isAttachedToWindow(this)) { //   
        action(this)  //        
    } else {
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View) {}

            override fun onViewDetachedFromWindow(view: View) {
                removeOnAttachStateChangeListener(this)
                action(view)
            }
        })
    }
}


extension:



fun View.postDelayedSafe(delayMillis: Long, block: () -> Unit) {
        val runnable = Runnable { block() }
        postDelayed(runnable, delayMillis)
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View) {}

            override fun onViewDetachedFromWindow(view: View) {
                removeOnAttachStateChangeListener(this)
                view.removeCallbacks(runnable)
            }
        })
}


. . , . Native Android 2 — Rx Coroutines.

.



, 100% . //.



Coroutines



, di . :



class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes), CoroutineScope by MainScope() {

    override fun onDestroyView() {
        super.onDestroyView()
        coroutineContext[Job]?.cancelChildren()
    }

    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}


onDestroyView, scope, View Fragment. Fragment .



onDestroy scope, .



.

postDelayed:



fun BaseFragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
    view ?: return null
    return launch {
        delay(delayMillis)
        action()
    }
}


, , view , null. . view, .



Keanu_Reevesandroidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01以降に接続できます。すでに既製のスコープがあります。



viewLifecycleOwner.lifecycleScope


fun Fragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
    view ?: return null
    return viewLifecycleOwner.lifecycleScope.launch {
        delay(delayMillis)
        action()
    }
}


処方箋



RXでは、Disposableクラスがサブスクリプションのキャンセルを担当しますが、RXでは、coroutineとは異なり、構造化された同時実行性はありません。このため、すべて自分で処方する必要があります。通常は次のようになります。



interface DisposableHolder {
    fun dispose()
    fun addDisposable(disposable: Disposable)
}

class DisposableHolderImpl : DisposableHolder {
    private val compositeDisposable = CompositeDisposable()

    override fun addDisposable(disposable: Disposable) {
        compositeDisposable.add(disposable)
    }

    override fun dispose() {
        compositeDisposable.clear()
    }
}


また、同じ方法でベースフラグメント内のすべてのタスクをキャンセルします。



class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes),
    DisposableHolder by DisposableHolderImpl() {

    override fun onDestroyView() {
        super.onDestroyView()
        dispose()
    }

    override fun onDestroy() {
        super.onDestroy()
        dispose()
    }
}


そして拡張機能自体:



fun BaseFragment.delayActionSafe(delayMillis: Long, block: () -> Unit): Disposable? {
    view ?: return null
    return Completable.timer(delayMillis, TimeUnit.MILLISECONDS).subscribe {
        block()
    }.also {
        addDisposable(it)
    }
}


拘留されて



遅延アクションを使用する場合、これはすでに非同期実行であることを忘れてはなりません。したがって、キャンセルが必要です。そうしないと、メモリリーク、クラッシュ、その他のさまざまな予期しないことが発生し始めます。




All Articles