Asynchrones Arbeiten mit View mithilfe von Coroutine

Schauen wir uns eine Situation an, in der wir eine Ansicht wie ImageView haben, die wir vor dem Rendern zuerst vorbereiten müssen - zum Beispiel ihre Größe, Form berechnen oder einen Blu-ray-Effekt anwenden usw. Diese Berechnungen können eine teure Operation sein. Es ist daher besser, sie in den Hintergrund-Thread zu übertragen.

Javista-Großväter erstellen ein Runabl und verwenden dann den Handler, um das Ergebnis in den Hauptstrom zu übertragen und es auf die Ansicht anzuwenden (das erste, was mir in den Sinn kommt).

Wie geht das schnell und bequem in einem Kessel mit seinen Coroutinen?

Erstellen Sie zunächst die Kotlin-Erweiterungsfunktion:

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

Jetzt simulieren wir eine Situation: Wir haben eine RecyclerView, in jedem Element befindet sich ein Bild. Vor der Show wollen wir dieses Bild verwischen (Unschärfe). So wäre es ohne 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)
    }
}

Ergebnis:



Wie Sie sehen, ist der Personalverlust erheblich.

Jetzt benutzen wir unsere Funktion:

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

Ergebnis:



Das Ganze ist, dass wir an den Lebenszyklus der Ansicht gebunden sind. Wenn die Ansicht vom übergeordneten Element getrennt wird und dies im Recycler ständig geschieht, wenn der Gegenstand verloren geht, oder im Fragment / in der Aktivität, wenn sie zerstört werden, können wir die Coroutine sicher anhalten und abbrechen, da wir wissen, dass das Ergebnis nirgends angezeigt werden kann und die Ansicht zur Zerstörung bereit ist.

Um es klar und verständlich zu machen, empfehle ich, solchen Code in Ihren ViewHolder zu schreiben und die Protokolle zu lesen:

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

Und Sie werden sehen, auf welchem ​​Viewholder welche Coroutine zu arbeiten beginnt und auf welchem ​​sie abgebrochen wird und nicht mehr funktioniert.

All Articles