Trabajando con View asincrónicamente usando corutina

Veamos una situación en la que tenemos una vista, por ejemplo, ImageView, que primero debemos preparar antes de renderizar, por ejemplo, calcular su tamaño, forma o aplicar un efecto blu-ray, etc. Estos cálculos pueden ser una operación costosa, por lo que es mejor transferirlos al hilo de fondo.

Los abuelos de Javista crearán un runabl y luego usarán el controlador para transferir el resultado a la transmisión principal y usarlo en la vista (lo primero que viene a la mente).

¿Cómo puede hacerse esto rápida y convenientemente en un caldero con sus corutinas?

Primero, cree la función de extensión kotlin:

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

Ahora simularemos la situación: tenemos un RecyclerView, en cada elemento hay una imagen. Antes del espectáculo, queremos desenfocar esta imagen (desenfoque). Así es como sería sin asincronización:

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 puede ver, la pérdida de personal es significativa.

Ahora usamos nuestra función:

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:



todo el asunto es que estamos apegados al ciclo de vida de la vista. Por lo tanto, cuando la vista se separa del padre, y esto sucede constantemente en el reciclador cuando se pierde el artículo, o en el fragmento / actividad cuando se destruyen, podemos detener y cancelar la rutina de manera segura, sabiendo que el resultado no se puede mostrar, la vista está lista para la destrucción.

Para que sea claro y comprensible, recomiendo escribir dicho código en su ViewHolder y mirar los registros:

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

Y verá en qué Viewholder qué corutina comienza a funcionar y en qué se cancela y deja de funcionar.

All Articles