Por qué postDelayed es peligroso

A menudo, debido a las peculiaridades del sistema android y sdk, tenemos que esperar hasta que se configure una determinada parte del sistema o se produzca algún evento que necesitemos. Esto suele ser una muleta, pero a veces no puede prescindir de ella, especialmente cuando se cumplen los plazos. Por lo tanto, muchos proyectos utilizaron postDelayed para esto. Debajo del corte, consideraremos por qué es tan peligroso y qué hacer al respecto.



Problema



Primero, veamos cómo se usa generalmente postDelayed ():



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


Se ve bien, pero echemos un vistazo más de cerca a este código:



1) Esta es una acción diferida que esperaremos un tiempo para completar. Sabiendo cuán dinámicamente el usuario puede realizar transiciones entre pantallas, esta acción debe cancelarse al cambiar un fragmento. Sin embargo, esto no sucede aquí, y nuestra acción se ejecutará incluso si se destruye el fragmento actual.



Es fácil de comprobar. Creamos dos fragmentos, al cambiar al segundo, ejecutamos postDelayed con un tiempo largo, por ejemplo 5000 ms. Inmediatamente volvemos. Y al cabo de un rato vemos en los logs que la acción no ha sido cancelada.



2) El segundo "sigue" al primero. Si en este ejecutable pasamos una referencia a la propiedad de nuestro fragmento, se producirá una pérdida de memoria, ya que la referencia al ejecutable vivirá más tiempo que el propio fragmento.



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, puede conectar androidx.lifecycle: lifecycle-runtime-ktx: 2.2.0-alpha01 o superior y ya tendremos un alcance listo para usar :



viewLifecycleOwner.lifecycleScope


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


RX



En RX, la clase Disposable es responsable de cancelar las suscripciones, pero en RX no hay concurrencia estructurada, a diferencia de la corrutina. Debido a esto, debe recetarlo todo usted mismo. Suele tener este aspecto:



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()
    }
}


También cancelamos todas las tareas en el fragmento base de la misma manera:



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

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

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


Y la propia extensión:



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


En custodia



Al utilizar acciones diferidas, no debemos olvidar que esta ya es una ejecución asincrónica y, en consecuencia, requiere cancelación, de lo contrario, comienzan a ocurrir pérdidas de memoria, fallas y otras cosas inesperadas.




All Articles