Trabalhando com o View de forma assíncrona usando coroutine

Vejamos uma situação em que temos uma visualização, por exemplo, ImageView, que devemos preparar primeiro antes da renderização - por exemplo, calcule seu tamanho, forma ou aplique um efeito blu-ray, etc. Esses cálculos podem ser uma operação cara, por isso é melhor transferi-los para o encadeamento em segundo plano.

Os avós Javista criarão um runabl e, em seguida, usarão o manipulador para transferir o resultado para o fluxo principal e usá-lo na exibição (a primeira coisa que vem à mente).

Como isso pode ser feito de forma rápida e conveniente em um caldeirão com suas corotinas:

Primeiro, crie a função kotlin-extension:

inline fun <T> View.doAsync(
        crossinline backgroundTask: (scope: CoroutineScope) -> T, 
        crossinline result: (T?) -> Unit) {
    val job = CoroutineScope(Dispatchers.Main)
    //  ,    
    // ,   
    val attachListener = object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(p0: View?) {}
        override fun onViewDetachedFromWindow(p0: View?) {
            job.cancel()
            removeOnAttachStateChangeListener(this)
        }
    }
    this.addOnAttachStateChangeListener(attachListener)
    //  Job,      
    job.launch {
        //  Deferred     
        val data = async(Dispatchers.Default) {
            try {
                backgroundTask(this)
            } catch (e: Exception) {
                e.printStackTrace()
                return@async null
            }
        }
        if (isActive) {
            try {
                result.invoke(data.await())
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        //     Job
        this@doAsync.removeOnAttachStateChangeListener(attachListener)
    }
}

Agora simularemos a situação: temos um RecyclerView, em cada item há uma imagem. Antes do show, queremos desfocar esta imagem (desfoque). Aqui está como seria sem assincronização:

inner class PostHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val ivTest = itemView.iv_test
    fun bind() {
        val bitmap = ... 
        val blurBitmap = bitmap?.addBlurShadow(Color.CYAN, 50.dp, 50.dp)
        ivTest.setImageBitmap(blurBitmap)
    }
}

Resultado:



como você pode ver, a perda de pessoal é significativa.

Agora usamos nossa função:

inner class PostHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val ivTest = itemView.iv_test
    fun bind() {
        val bitmap = ... 
        itemView.doAsync({ scope -> 
            //        
            return@doAsync bitmap?.addBlurShadow(Color.CYAN, 50.dp, 50.dp)
        }, { it ->
            //            
            ivTest.setImageBitmap(it)
        })
    }
}

Resultado:



a coisa toda é que estamos apegados ao ciclo de vida da visualização. Portanto, quando a visualização é desanexada do pai, e isso acontece constantemente no reciclador quando o item é perdido, ou no fragmento / atividade quando eles são destruídos, podemos parar e cancelar a corotina com segurança, sabendo que não há lugar para exibir o resultado, a visualização está pronta para destruição.

Para deixar claro e compreensível, recomendo escrever esse código no seu ViewHolder e examinar os logs:

itemView.doAsync({ scope ->
    logInfo("coroutine start")
    var x = 0
    //        scope.isActive
    //       isActive = true,     
    //    ,     
    while (x < 100 && scope.isActive) {
        TimeUnit.MILLISECONDS.sleep(100)
        logInfo("coroutine, position: $adapterPosition ${x++}")
    }
    logInfo("coroutine end")
}, {
    logInfo("coroutine DONE")
})

E você verá em qual Viewholder qual corotina começa a funcionar e em qual é cancelada e para de funcionar.

All Articles