为什么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)这是一个延迟的操作,我们将等待一段时间才能完成。知道用户可以在屏幕之间进行切换的动态程度,因此在更改片段时应取消此操作。但是,这不会在这里发生,即使当前片段被破坏,我们的动作也将执行。



很容易检查。我们创建两个片段,当切换到第二个片段时,我们运行postDelayed的时间很长,例如5000 ms。我们马上回去。一段时间后,我们在日志中看到该操作尚未取消。



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_Reeves,您可以连接androidx.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中,与协程不同,没有结构化并发。因此,您必须自己开处方。通常看起来像这样:



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