Деды-джависты создадут ранабл и потом при помощи хэндлера перенесут результат в основной поток и применят на вьюхе (первое, что приходит в голову).
Как это можно сделать быстро и удобно в котлине с его корутинами:
Для начала создадим 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) } }
Теперь смоделируем ситуацию: у нас есть RecyclerView, в каждом айтеме есть картинка. Перед показом мы хотим эту картинку заблюрить (размыть). Вот как это будет без асинхронщины:
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) } }
Результат:

Как видно — потеря кадров существенная.
Теперь используем нашу функцию:
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) }) } }
Результат:

Вся фишка в том, что мы привязываемся к жизненному циклу вью. Поэтому, когда вьюха открепляется от родителя, а это происходит в ресайклере постоянно при потере видимости айтема, или во фрагменте\активити при их уничтожении, то мы можем спокойно остановить и отменить корутину, зная, что результат выводить уже некуда, вьюха готова к уничтожению.
Чтобы было наглядно и понятно, рекомендую в вашем ВьюХолдере написать такой код и посмотреть логи:
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") })
И вы увидите, на каком ВьюХолдере какая корутина начинает работать, а на каком отменяется и прекращает работу.
ссылка на оригинал статьи https://habr.com/ru/post/492604/
Добавить комментарий