Работaем с View асинхронно с использованием корутин

от автора

Давайте рассмотрим ситуацию, когда у нас есть вьюха, например ImageView, которую мы сначала должны подготовить перед отрисовкой — например, вычислить ее размеры, форму, или применить блюр-эффект и т.д. Эти вычисления могут стать дорогостоящей операцией, поэтому лучше перенести их в фоновый поток.

Деды-джависты создадут ранабл и потом при помощи хэндлера перенесут результат в основной поток и применят на вьюхе (первое, что приходит в голову).

Как это можно сделать быстро и удобно в котлине с его корутинами:

Для начала создадим 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *