25 августа 2022

Как работает WorkManager в Android

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

Как бороться с Android'ом и не позволять ему уничтожать важные фоновые процессы, расскажет Сергей Смирнов, Android-разработчик CleverPumpkin, который так овладел инструментом WorkManager, что смог в фоновом режиме написать эту статью.

How the WorkManager works in Android
Что такое WorkManager
WorkManager появился как инструмент для отложенной фоновой работы. Начиная с первого выпуска, он предоставлял несколько базовых API:

  1. Запуск отложенных задач. Это позволяло разгрузить приложение от задач, выполнение которых не требуется немедленно - например, синхронизации с сервером в определенное время суток.

  2. Constraint'ы (Констрейнты, ограничения) позволяли запускать задачи на основе определенных состояний устройства – например, только при наличии сети, во время зарядки или в спящем режиме.

  3. Возможность реализовать последовательность или цепочку из нескольких задач, которые зависят от выполнения предыдущих.

  4. WorkManager поддерживал не только разовые задачи, но и задачи, которые должны выполняться периодически.

  5. А также WorkManager позволял узнать и подписаться на статус выполнения: находится ли работа в очереди, выполняется, заблокирована или завершена.
Изначально это был инструмент только для отложенной фоновой работы, но в последних версиях Workmanager появилось несколько новых функций. Так, теперь появилась возможность запросить немедленное выполнение задачи, если приложение находится на переднем плане, и быть при этом уверенным в завершении этой работы. Например, приложение может продолжать обрабатывать фото, загружая их на сервер, даже если пользователь уже делает что-то другое.

В больших и сложных приложениях WorkManager помогает оптимизировать и эффективно использовать ресурсы, выделяя работу в отдельный процесс.

Кроме того, появились API-интерфейсы, позволяющие тестировать выполнение одиночных, повторяющихся и последовательных задач. Также улучшена инструментальная поддержка – теперь можно следить за выполнением задач прямо из Android-студии.
WorkManager имеет свою базу данных, что позволяет сохранять информацию и статус работы в случае сбоя процесса или даже перезагрузки устройства. Это дает возможность восстановить статус выполнения работы и продолжить его с того места, на котором остановились, независимо от обстоятельств.

То есть, теперь WorkManager - не только инструмент для выполнения отложенных фоновых задач, но и инструмент для любой задачи, которая обязательно должна быть выполнена и завершена. Google рекомендует WorkManager как лучшее решение для устойчивого выполнения каких-либо задач в global scope: если ваше приложение работает, то задача будет выполнена даже при смене ориентации/конфигурации и других остановок Activity.
Как WorkManager заставляет работать фоном
Давайте рассмотрим, как WorkManager поможет нам с выполнением длительных задач, даже когда пользователь переводит их в фоновый режим.

Стоит напомнить о сервисах – программных компонентах. Существуют background сервис – он же сервис для выполнения фоновых задач; и foreground сервис, который при выполнении таких же фоновых задач показывает пользователю уведомление, информирующее о том, что приложение запущено.

Пока приложение на переднем плане, мы можем запускать любые сервисы для фоновых задач. Но когда приложение переводится в фон, оно получает от системы еще несколько минут на выполнение задач, после чего по воле Android отключается. У foreground сервиса меньше шансов на уничтожение, но система все равно попытается за счет фонового процесса освободить ресурсы для основного на этот момент приложения. В этой ситуации мы не можем надеяться на то, что наши задачи будут выполнены – и тем более возобновлены позже. Выполнение длительных задач в этой ситуации становится практически невозможным.

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

Чтобы сделать это возможным, WorkManager связывает жизненный цикл процесса, в котором выполняется работа, с жизненным циклом foreground service. WorkManager по-прежнему осведомлен об этой работе, но foreground service полностью владеет жизненным циклом выполнения.

Так как foreground service требует показ уведомления пользователю, разработчики добавили API для WorkManager. Также WorkManager предоставляет API-интерфейсы, чтобы пользователь мог остановить выполнение работы прямо из уведомления.

Перейдем к практике и разберемся на примерах в новых возможностях WorkManager.
Давайте создадим наш Worker, который загружает данные по частям и сохраняет их в файл. Есть разные типы Worker'ов, и можно использовать подходящий вам. В данном примере мы возьмем CoroutineWorker. Определим несколько нужных нам методов: notification(), к реализации которого мы еще вернемся, и download(), который загружает контент по ссылке, сохраняет в файл и использует Callback для отображения прогресса.
Также добавим метод createForegroundInfo(), который возвращает экземпляр Foregroundinfo. Для его создания нужно передать Notification ID и объект Notification.
Класс ForegroundInfo предоставляется библиотекой WorkManager'а.
Далее переопределим основной метод doWork(), в котором вызовем метод download(). Обратите внимание на вызов метода setForeground() - именно вызов этого метода помечает задачу как длительную и заботится о запуске foreground service.

Если все прошло успешно, в конце вернем Result.Success, и WorkManager сам позаботится о том, чтобы завершить foreground service и закрыть уведомление.
Вернемся к созданию уведомления. Как я говорил, WorkManager предоставляет API для отмены выполнения длительной работы прямо из уведомления, если пользователь передумал. Это производится вызовом метода createCancelPendingIntent(), который создает pending intent для отмены.
И при создании уведомления мы вызываем метод addAction(), куда передаем этот интент. Метод addAction() добавит кнопку в уведомление, для отмены задачи:
Это надо сделать срочно!
Но! Android 12 вводит новые ограничения на foreground сервисы: приложение не может запустить этот сервис, если находится в фоне. Таким образом, вызов метода setForeground также может бросить исключение, если запуск foreground сервиса не разрешен. Поэтому нам нужно указать WorkManager'у, что мы запускаем так называемую Expedited Work - срочную работу.
Так как Expedited Work выполняется в контексте foreground сервиса, нам нужно переопределить метод getForegroundInfo() – там мы возвращаем экземпляр foregroundInfo, принимающий на вход экземпляр notification и notification id. Интересно, что на Android 12 не будет показано никакого уведомления, так как мы пометили работу как срочную.
При создании нашего запроса для WorkManager'а мы можем использовать метод setExpedited() (куда мы должны передать, как поступить, если квота исчерпана). Мы можем либо поменять нашу работу на обычную, а не ускоренную, либо можем вовсе ее отменить.

Таким образом, метод setExpedited сообщит WorkManager'у, что workrequest важен с точки зрения пользователя, и наша задача является срочной и приоритетной к выполнению:
Надеюсь, теперь мы объяснили, как WorkManager вам поможет в решении проблемы устойчивого выполнения задач. Возможности его API позволяют комфортно заменить устаревшие инструменты фонового планирования Android, включая FirebaseJobDispatcher, GcmNetworkManager и Job Scheduler. WorkManager легко интегрируется с Coroutines и RxJava и предоставляет возможность подключения собственных асинхронных API .
Есть идея?