Por que postDelayed é perigoso

Freqüentemente, devido às peculiaridades do sistema android e sdk, precisamos esperar até que uma determinada parte do sistema seja configurada ou algum evento de que necessitamos ocorra. Muitas vezes, é uma muleta, mas às vezes você não pode ficar sem ela, especialmente em um ambiente de prazos. Portanto, muitos projetos usados ​​postDelayed para isso. Abaixo do corte, consideraremos por que ele é tão perigoso e o que fazer a respeito.



Problema



Primeiro, vamos ver como postDelayed () é comumente usado:



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


Parece bom, mas vamos dar uma olhada mais de perto neste código:



1) Esta é uma ação adiada que esperaremos algum tempo para ser concluída. Sabendo o quão dinamicamente o usuário pode fazer transições entre telas, esta ação deve ser cancelada ao alterar um fragmento. Porém, isso não acontece aqui, e nossa ação será executada mesmo se o fragmento atual for destruído.



É fácil verificar. Criamos dois fragmentos, ao mudar para o segundo, executamos postDelayed com muito tempo, por exemplo 5000 ms. Nós imediatamente voltamos. E depois de um tempo, vemos nos logs que a ação não foi cancelada.



2) O segundo "segue" do primeiro. Se neste executável passarmos uma referência à propriedade do nosso fragmento, ocorrerá um vazamento de memória, pois a referência ao executável viverá mais que o próprio 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, você pode conectar androidx.lifecycle: lifecycle-runtime-ktx: 2.2.0-alpha01 ou superior e já teremos um escopo pronto:



viewLifecycleOwner.lifecycleScope


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


RX



No RX, a classe Disposable é responsável por cancelar as assinaturas, mas no RX não há concorrência estruturada, ao contrário da co-rotina. Por isso, você deve prescrever tudo sozinho. Geralmente é assim:



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


Também cancelamos todas as tarefas no fragmento de base da mesma maneira:



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

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

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


E a própria extensão:



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


Em custódia



Ao usar qualquer ação adiada, não devemos esquecer que esta já é uma execução assíncrona e, portanto, requer o cancelamento, caso contrário, vazamentos de memória, travamentos e várias outras coisas inesperadas começam a ocorrer.




All Articles