![](https://habrastorage.org/getpro/habr/upload_files/4ab/5fb/78a/4ab5fb78a4a211885dd07522fdd09891.png)
Всем привет! Настало время для новой статьи, я решил сделать еще один перевод. Приятного чтения и погружения в Kotline Coroutines!
С самого начала определимся с парой фундаментальных определений, которые будут использоваться в этой статье.
Handler (подробнее)
A Handler allows you to send and process
Message
and Runnable objects associated with a thread'sMessageQueue
. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to aLooper
. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread. — Android Develoeprs
Другими словами, это инструмент благодаря которому можно отправлять какие-либо действия в другой поток. Следовательно тяжелые операции, такие как чтение файловой системы или получение/отправка данных по сети, могут выполняться в фоне и не загружать основной поток.
Looper (подробнее)
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call
prepare()
in the thread that is to run the loop, and thenloop()
to have it process messages until the loop is stopped. — Android Develoeprs
Если на русском, то Looper - это класс используемый для отправки сообщения в цикл потока. Потоки изначально не имеют цикла сообщений, ассоциированного с ними, в связи с чем, его нужно создать используя метод prepare()
и затем loop()
чтобы поток начал обрабатывать сообщения по очереди, до полной остановки.
В последних версиях Android использование Handler'ов без Looper'а стало устаревшим, а связано это с тем, что подобное поведение часто приводит к багам связанным с потерями сообщений, причем без уведомления, и крашам. Согласно документации, так же есть возможность использования Executor'a
, либо же получить обработчик конкретной view, как замену, но таким даже в таком случае приложение может грохнуться, так как не связанно с Lifecycle'ом
.
Подробнее про Lifecycle
можно почитать здесь.
Давайте посмотрим, как мы можем применить Kotlin Coroutines и Lifecycle библиотеки чтобы заменить Handler().postDelay() метод и безопасно вызвать какое-либо действие после определенного промежутка времени.
Handlers: старомодный способ
Не смотря на то, что конструктор Handler()
является устаревшим, у нас есть возможность получить обработчик View вызвав getHandler()
, либо же можно просто вызвать метод postDelay()
. Таким образом вы не будете использовать устаревший метод и будет возможность приостановить выполнение действия на определенное время.
![](https://habrastorage.org/getpro/habr/upload_files/116/ad2/c83/116ad2c83f725b404bafc2cf7a9f4b9e.png)
Давайте посмотрим как это работает под капотом! Чтобы запустить код на обработчике, необходимо подучить доступ к ассоциированному к нему потоку, на котором работает View. Каждый раз, когда действие будет запущено используя postDelay, новое действие будет добавлено в очередь сообщений. Однако помните, что работа будет происходить на основном потоке!
Делая это, важно помнить, что ваше отсроченное действие может работать, даже когда View уже будет уничтожено, что может привести к неожиданному поведению и даже вылетам приложения.
Kotlin + Coroutine + Lifecycle
Давайте посмотрим, как сделать лучшее решение, чем использование Handler().postDelay()
, используя Kotlin, Coroutines и Lifecycle библиотеках. Вначале, необходимо будет подключить следующие библиотеки.
![](https://habrastorage.org/getpro/habr/upload_files/d3d/b04/7ed/d3db047eda66615ddef9a7b968df9aff.png)
Теперь мы готовы. Чтобы достичь безопасного ожидания, необходимо найти lifecycleOwner необходимой View, используя findViewTreeLifecycleOwner()
метод предоставленный Lifecycle библиотекой.
Затем, используя Coroutine Scope полученного Lifecycle, мы можем запустить новую сопрограмму с выбранным диспетчер, мы будем использовать Main Dispatcher, так как хотим выполнить действия на основном потоке. Чтобы выполнить действия в сопрограмме с задержкой, мы применим функцию delay()
.
![](https://habrastorage.org/getpro/habr/upload_files/619/5fc/4ea/6195fc4ea0c910ae2ed3d6250ad368a1.png)
Теперь можем вызвать функцию delayOnLifecycle()
, которая теперь является extension-function для любой View.
![](https://habrastorage.org/getpro/habr/upload_files/91a/491/e12/91a491e12312717c5e295b783807a128.png)
Заключение
Всегда было болезненно управлять действиями на нескольких потоках, так как было необходимо смотреть состояние Lifecycle, Fragment и так далее. Однако благодаря Kotlin Coroutines и Lifecycle библиотеках, мы имеем более безопасное решение.
А вы уже используете Kotline Coroutines в своем проекте?
Большое спасибо, что прочитали мою новую статью, а так же огромное спасибо автору оригинала, Julien Salvi и Olivier Buiron. Я надеюсь на обратную связь!
Nihiroz
А что будет, если во время выполнения корутины мы удалим View с экрана и произойдет `onDetachedFromWindow`? Судя по всему корутина не отменится и произойдет утечка памяти. К тому же выполнение асинхронных операций внутри View говорит о плохой архитектуре, желательно выпость такие операции в ViewModel или другие подобные штуки.
Отдельное спасибо за код в виде картинок, очень удобно копировать
alyxe Автор
Да, действительно, если удалить View с экрана, а корутина была запущена во View, то необходимо проследить и отменить выполнение корутины.
Действительно если какая-то лишняя логика, особенно асинхронная, выполняется внутри View - это не очевидное и проблемное решение. Однако никто не мешает имплементировать анимацию используя корутины.
Да, я думал делать картинками или кодом, но решил сохранить в этом случае, как в оригинале. В следующий раз, постараюсь делать либо ссылку на GitHub, либо вставлять код вместо картинок.