Про плюсы и минусы мы уже поговорили, так что теперь узнаем как создать WorkManager и какой выбрать.
Всего есть 4 варианта:
- 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
- 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
- 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
- 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(). Всё плохо, но мы не будем ничего с этим делать.
Воркер может быть одноразовым или многоразовым. И тут для двух вариантов целых три метода запуска:
- WorkManager.getInstance().enqueue(workRequest). Самый простой вариант.
- WorkManager.getInstance().enqueueUniqueWork(uniqueWorkName, existingWorkPolicy, workRequest).
- 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