Utilisation de View de manière asynchrone à l'aide de coroutine

Examinons une situation où nous avons une vue, par exemple ImageView, que nous devons d'abord préparer avant le rendu - par exemple, calculer sa taille, sa forme ou appliquer un effet Blu-ray, etc. Ces calculs peuvent être une opération coûteuse, il est donc préférable de les transférer vers le thread d'arrière-plan.

Les grands-pères Javista créeront un runabl puis utiliseront le gestionnaire pour transférer le résultat vers le flux principal et l'utiliser sur la vue (la première chose qui vient à l'esprit).

Comment cela peut-il être fait rapidement et facilement dans un chaudron avec ses coroutines:

Créez d'abord la fonction 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)
    }
}

Nous allons maintenant simuler la situation: nous avons un RecyclerView, dans chaque élément il y a une image. Avant le spectacle, nous voulons rendre cette image floue (flou). Voici comment cela se passerait sans asynchronisation:

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

Résultat:



comme vous pouvez le constater, la perte de personnel est importante.

Maintenant, nous utilisons notre fonction:

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

Résultat:



le tout est que nous sommes attachés au cycle de vie de la vue. Par conséquent, lorsque la vue est détachée du parent, et cela se produit constamment dans le recycleur lorsque l'article est perdu, ou dans le fragment / l'activité lorsqu'ils sont détruits, nous pouvons arrêter et annuler la coroutine en toute sécurité, sachant qu'il n'y a nulle part où afficher le résultat, la vue est prête à être détruite.

Pour le rendre clair et compréhensible, je recommande d'écrire un tel code dans votre ViewHolder et de consulter les journaux:

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

Et vous verrez sur quel Viewholder quelle coroutine commence à fonctionner et sur laquelle elle est annulée et cesse de fonctionner.

All Articles