Всем привет! Настало время для новой статьи, я решил сделать еще один перевод. Приятного чтения и погружения в Kotline Coroutines!

С самого начала определимся с парой фундаментальных определений, которые будут использоваться в этой статье.

Handler (подробнее)

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. 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 a Looper. 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 then loop() 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(). Таким образом вы не будете использовать устаревший метод и будет возможность приостановить выполнение действия на определенное время.

Давайте посмотрим как это работает под капотом! Чтобы запустить код на обработчике, необходимо подучить доступ к ассоциированному к нему потоку, на котором работает View. Каждый раз, когда действие будет запущено используя postDelay, новое действие будет добавлено в очередь сообщений. Однако помните, что работа будет происходить на основном потоке!

Делая это, важно помнить, что ваше отсроченное действие может работать, даже когда View уже будет уничтожено, что может привести к неожиданному поведению и даже вылетам приложения.

Kotlin + Coroutine + Lifecycle

Давайте посмотрим, как сделать лучшее решение, чем использование Handler().postDelay(), используя Kotlin, Coroutines и Lifecycle библиотеках. Вначале, необходимо будет подключить следующие библиотеки.

Теперь мы готовы. Чтобы достичь безопасного ожидания, необходимо найти lifecycleOwner необходимой View, используя findViewTreeLifecycleOwner() метод предоставленный Lifecycle библиотекой.

Затем, используя Coroutine Scope полученного Lifecycle, мы можем запустить новую сопрограмму с выбранным диспетчер, мы будем использовать Main Dispatcher, так как хотим выполнить действия на основном потоке. Чтобы выполнить действия в сопрограмме с задержкой, мы применим функцию delay().

Теперь можем вызвать функцию delayOnLifecycle(), которая теперь является extension-function для любой View.

Заключение

Всегда было болезненно управлять действиями на нескольких потоках, так как было необходимо смотреть состояние Lifecycle, Fragment и так далее. Однако благодаря Kotlin Coroutines и Lifecycle библиотеках, мы имеем более безопасное решение.

А вы уже используете Kotline Coroutines в своем проекте?


Большое спасибо, что прочитали мою новую статью, а так же огромное спасибо автору оригинала, Julien Salvi и Olivier Buiron. Я надеюсь на обратную связь!

Комментарии (2)


  1. Nihiroz
    01.09.2021 22:23
    +3

    А что будет, если во время выполнения корутины мы удалим View с экрана и произойдет `onDetachedFromWindow`? Судя по всему корутина не отменится и произойдет утечка памяти. К тому же выполнение асинхронных операций внутри View говорит о плохой архитектуре, желательно выпость такие операции в ViewModel или другие подобные штуки.

    Отдельное спасибо за код в виде картинок, очень удобно копировать


    1. alyxe Автор
      02.09.2021 12:18

      Да, действительно, если удалить View с экрана, а корутина была запущена во View, то необходимо проследить и отменить выполнение корутины.

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

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