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.launch {
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()
}
}
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
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.