Найти тему

WorkManager. Какие есть и как создать. Часть 2.

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

Всего есть 4 варианта:

  1. Worker — выполняется в фоне. Для работы подключаем "android.arch.work:work-runtime-ktx:1.0.1". Тут важно не забыть переопределить onStopped и отменить какие-то подписки и прочие вещи. Пример: https://github.com/Ladgertha/WorkManagers/blob/master/app/src/main/java/ru/ladgertha/workmanagers/ExampleWorker.kt
  2. CoroutineWorker — работает с корутинами. В документации советуют использовать именно это. Выполняется на дефолтном диспатчере, но можно сменить на другой. Подключается через стандартную зависимость "android.arch.work:work-runtime-ktx:1.0.1". А еще тут автоматически обрабатывается onStopped и отменяется корутина. Ну разве не здорово? Пример: https://github.com/Ladgertha/WorkManagers/blob/master/app/src/main/java/ru/ladgertha/workmanagers/ExampleCoroutineWorker.kt
  3. RxWorker — работает с RxJava. Вызывается на main-потоке, но выполняется в фоне. И вот тут уже понадобятся дополнительные зависимости work-rxjava3 или work-rxjava2 ("androidx.work:work-rxjava2:2.7.1"). Здесь автоматически обрабатывается onStopped и отменяется подписка, что довольно удобно. Кстати, у меня почему-то RxWorker и CoroutineWorker отказывались существовать вместе и кидали ошибку, поэтому для Rx пришлось закомментировать. Пример: https://github.com/Ladgertha/WorkManagers/blob/master/app/src/main/java/ru/ladgertha/workmanagers/ExampleRxWorker.kt
  4. ListenableWorker — базовый класс для перечисленных выше воркеров. Все наследуются от него. Здесь понадобится дополнительная зависимость "androidx.concurrent:concurrent-futures:1.1.0". Также надо не забыть обработать нужные вещи в onStopped. Выполняется на Main-потоке. Пример: https://github.com/Ladgertha/WorkManagers/blob/master/app/src/main/java/ru/ladgertha/workmanagers/ExampleListenableWorker.kt

Во всех воркерах у нас обязательно переопределяется один метод, где выполняется вся работа. Первые два воркера возвращают Result. RxWorker возвращает Single<Result>. ListenableWorker возвращает ListenableFuture<Result>.

Result — это тип с тремя состояниями. Может быть:

  • Result.success(). Всё прошло успешно и задача выполнена.
  • Result.retry(). Что-то пошло не так и мы хотим попробовать выполнить задачу ещё раз. Нужно не забыть указать setBackoffCriteria (про него ниже), а то там дефолтное значение в 10 секунд (воркер попытается повторить задачу через 10 секунд).
  • Result.failure(). Всё плохо, но мы не будем ничего с этим делать.

Воркер может быть одноразовым или многоразовым. И тут для двух вариантов целых три метода запуска:

  1. WorkManager.getInstance().enqueue(workRequest). Самый простой вариант.
  2. WorkManager.getInstance().enqueueUniqueWork(uniqueWorkName, existingWorkPolicy, workRequest).
  3. WorkManager.getInstance().enqueueUniquePeriodicWork(uniqueWorkName, existingPeriodicWorkPolicy, workRequest).

Я советую использовать именно последние два варианта, чтобы можно было указать имя и existingWorkPolicy.

uniqueWorkName — уникальное имя нашего воркера. Это очень важный параметр, если мы хотим как-то взаимодействовать с воркером (отменять, следить за ним, получать данные), а не просто запустить в фоне и забыть.

existingWorkPolicy/existingPeriodicWorkPolicy — это очень важная и полезная вещь. Представьте, что у нас запустился один воркер, работает и делает что-то, а внезапно запустился еще один воркер с таким же именем. И вот тут мы как раз можем указать как разруливать такие ситуации.

Для одноразового воркера возможны три варианта:

  • REPLACE (второй воркер заменяет первый)
  • KEEP (игнорируем второй и продолжаем выполнять первый)
  • APPEND (запустим второй после выполнения первого. Тут важно, что если первый упадёт с ошибкой, то второй отменится).
  • В новых версиях ещё есть APPEND_OR_REPLACE — почти как APPEND, но тут, если первый воркер упадёт, то второй его заменит.

Для периодического воркера возможны два варианта REPLACE или KEEP. Может быть уже добавлены и другие.

Теперь самое интересное. Третьим параметром передаем некий workRequest. Как его создать и что это? Это как раз сам запрос со всеми необходимыми данными.

Выглядит он вот так:

Первая строчка: OneTimeWorkRequestBuilder<ExampleCoroutineWorker>() — обозначаем, что это одноразовый воркер и передаем его имя.

Для периодического воркера тут будет PeriodicWorkRequestBuilder<ExampleCoroutineWorker>(15, TimeUnit.MINUTES). Это единственное отличие с одноразовым воркером. Используем PeriodicWorkRequestBuilder и указываем период для запуска.

Вторая строчка: .setConstraints(getConstraints()). Она не обязательная. Тут мы указываем все параметры, при которых воркер должен запуститься и работать. Я указала, что нужно подключение по Wi-Fi, батарея не должна быть низкой, телефон должен стоять на зарядке и он не должен использоваться (setRequiresDeviceIdle есть только в API 23+). Есть еще и другие условия, но я решила остановиться, чтобы ситуация для запуска всё-таки была реальной.

Тут очень важный момент: даже если воркер запустился, но, например, телефон перестал заряжаться, то Constraint останавливает работу воркера, даже если он не успел выполниться.

Третья строчка: .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES). Она не обязательная. Это нужно только в том случае, если мы используем Result.retry(). Первым параметром передаем линейное или экспоненциальное время (EXPONENTIAL или LINEAR).

  • Если выбираем LINEAR, то время ожидания для повторного запуска будет линейно увеличиваться: 10-20-30-40-50... (попробовали запустить через 10 минут, не получилось — пробуем запустить через 20 минут — не получилось — пробуем через 30 минут и т.п.)
  • Если выбираем EXPONENTIAL, то 10-20-40-80....

Вторым параметром передаем время, после которого воркер попробует перезапуститься. Третьим — единицу измерения времени (часы, минуты и т.п.).

Четвертая строчка: .setInputData(getData(name)) — Она не обязательная. Тут можем передать в воркер какие-то данные. Это просто некий key-value объект. Почти аналог Bundle. Мой метод getData(name) выглядит так:

Внутри воркера сможем получить имя через inputData.getString(NAME_KEY).

Последняя строчка: build() — создаем запрос. Можно было при создании использовать только первую и последнюю строчки, но так делать не рекомендуется.

Готово. :)

Дубль статей в телеграмме — https://t.me/android_junior

Мои заметки в телеграмме — https://t.me/android_junior_notes