Bekerja dengan View asynchronous menggunakan coroutine

Mari kita lihat situasi di mana kita memiliki pandangan, misalnya, ImageView, yang pertama-tama harus kita persiapkan sebelum rendering - misalnya, menghitung ukurannya, bentuknya, atau menerapkan efek yang lebih biru, dll. Perhitungan ini bisa menjadi operasi yang mahal, jadi lebih baik untuk mentransfernya ke utas latar belakang.

Kakek Javista akan membuat runabl dan kemudian menggunakan pawang untuk mentransfer hasilnya ke aliran utama dan menggunakannya pada tampilan (hal pertama yang terlintas dalam pikiran).

Bagaimana ini bisa dilakukan dengan cepat dan mudah di dalam kuali dengan coroutine-nya:

Pertama, buat fungsi 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)
    }
}

Sekarang kita akan mensimulasikan situasi: kita memiliki RecyclerView, di setiap item ada gambar. Sebelum pertunjukan, kami ingin mengaburkan gambar ini (blur). Begini caranya tanpa sinkronisasi:

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

Hasil:



Seperti yang Anda lihat, kehilangan personel sangat signifikan.

Sekarang kita menggunakan fungsi kita:

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

Hasil:



Seluruhnya adalah bahwa kita melekat pada siklus hidup pandangan. Oleh karena itu, ketika pandangan terpisah dari induknya, dan ini terjadi terus-menerus di pendaur ulang ketika item hilang, atau dalam fragmen / aktivitas ketika mereka dihancurkan, kita dapat dengan aman menghentikan dan membatalkan coroutine, mengetahui bahwa hasilnya tidak dapat ditampilkan, tampilan siap untuk dihancurkan.

Untuk membuatnya jelas dan dapat dimengerti, saya sarankan menulis kode seperti itu di ViewHolder Anda dan melihat log:

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

Dan Anda akan melihat di mana Viewholder mana coroutine mulai bekerja, dan di mana itu dibatalkan dan berhenti bekerja.

All Articles