Важной частью задач приложения является фоновая работа. Его можно скачать или загрузить, сжать или распаковать, синхронизировать и т. д. Когда-то сервисы были предназначены для фоновой работы. Но в Android 8 они были сильно ограничены: если приложение не активно, то через какое-то время служба будет остановлена. И задолго до Android 8 разработчики начали использовать такие инструменты, как JobScheduler или Firebase JobDispatcher, для выполнения фоновых задач.
WorkManager позволяет запускать фоновые задачи последовательно или параллельно, передавать им данные, получать от них результаты, отслеживать статус выполнения и запускать только при соблюдении заданных условий.
Это на самом деле очень легко использовать.
Создадим и запустим фоновую задачу
- Добавьте в зависимости:
// (for Java only)
implementation("androidx.work:work-runtime:$work_version")
или
// for Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:$work_version")
- Создайте класс, который наследует
Worker
класс:
class MyWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters) {
private val TAG = this.javaClass.simpleName
override fun doWork(): Result {
Log.d(TAG, "doWork: start")
try {
TimeUnit.SECONDS.sleep(10)
} catch (e: InterruptedException) {
e.printStackTrace()
}
Log.d(TAG, "doWork: end")
return Result.success()
}
}
В doWork
методе нам предлагается разместить код, который будет выполняться. Здесь я просто делаю паузу на 10 секунд и возвращаюсь Result.success
, что означает, что все прошло успешно. Нам не нужно возиться с потоками, потому что код будет выполняться в потоке, отличном от пользовательского интерфейса.
- Теперь нам нужно обернуть
MyWorker
вWorkRequest
:
val workRequest: WorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkRequest
позволяет задать условия запуска и входные параметры задачи. Пока ничего не ставим, а просто создаем OneTimeWorkRequestBuilder
, на что говорим, что MyWorker
задачу нужно будет запустить.
OneTimeWorkRequestBuilder
имеет такое имя не просто так. Эта задача будет выполнена один раз. Есть еще PeriodicWorkRequest
, но об этом позже.
- Теперь вы можете запустить задачу:
WorkManager
.getInstance(this)
.enqueue(workRequest)
Берем WorkManager
и переходим WorkRequest
к его методу постановки в очередь. После этого задание будет запущено.
Смотрим лог:
2023-04-02 10:01:58.364 20001-20040 MyWorker D doWork: start
2023-04-02 10:02:08.367 20001-20040 MyWorker D doWork: end
Видно, что задача выполнялась 10 секунд, а код не выполнялся в UI-потоке.
Статус задачи
WorkManager предоставляет возможность отслеживать статус задачи. Например, в Activity мы пишем:
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(workRequest.id)
.observe(this) { workInfo ->
Log.d(TAG, "onChanged: " + workInfo.state)
}
Метод getWorkInfoByIdLiveData должен передавать идентификатор задачи, который может быть получен
WorkRequest.id
метод. В качестве состояния мы получаем LiveData, подписываемся на него, и все изменения статуса нашей задачи будут приходить в onChanged
метод. С помощью метода WorkInfo.state мы получим текущее состояние.
Мы запускаем:
2023-04-02 10:41:26.492 25791-25791 MainActivity D onChanged: ENQUEUED
2023-04-02 10:41:26.530 25791-25826 MyWorker D doWork: start
2023-04-02 10:41:26.531 25791-25791 MainActivity D onChanged: RUNNING
2023-04-02 10:41:36.531 25791-25826 MyWorker D doWork: end
2023-04-02 10:41:36.560 25791-25791 MainActivity D onChanged: SUCCEEDED
Сразу после вызова метода enqueue задача находится в статусе ENQUEUED. Затем WorkManager определяет, что задачу можно запустить, и выполняет наш код. В этот момент статус меняется на РАБОТАЕТ. После выполнения статус будет SUCCEEDED, потому что такой статус мы вернули в doWork
методе. Статус приходит к нам в поток пользовательского интерфейса.
Теперь снова запустим задачу и закроем приложение:
2023-04-02 10:50:38.057 25791-25791 MainActivity D onChanged: ENQUEUED
2023-04-02 10:50:38.067 25791-25923 MyWorker D doWork: start
2023-04-02 10:50:38.071 25791-25791 MainActivity D onChanged: RUNNING
2023-04-02 10:50:48.073 25791-25923 MyWorker D doWork: end
Обратите внимание, что задача выполнена, но статус SUCCEEDED не пришел. Почему? Потому что, закрыв Activity, мы просто отписались от LiveData, которая передала нам статус задачи. Но сама задача никуда не делась. Он никак не зависит от приложения и будет выполняться, даже если приложение закрыто.
Состояние
В нашей задаче мы вернули статус WorkInfo.SUCCESS, тем самым сообщив, что все ок. Есть еще два варианта:
FAILURE — в этом случае после выполнения задачи workInfo.state вернет FAILED. Для нас это сигнал о том, что задача не выполнена.
CANCELED — используется для обозначения того, что задача была отменена и не будет выполняться. Все зависимые задания также будут помечены как ОТМЕНЕННЫЕ и не будут выполняться.
ЗАБЛОКИРОВАНО — задача в настоящее время заблокирована, так как ее предварительные условия не были успешно выполнены.
Отменить задачу
Мы можем отменить задачу с помощью cancelWorkById
метода, передав идентификатор задачи:
WorkManager.getInstance(this).cancelWorkById(workRequest.id)
Это вызовет onStopped
метод в MyWorker
классе (если вы его реализовали). Кроме того, в MyWorker
классе мы всегда можем использовать логический isStopped
метод, чтобы проверить, была ли задача отменена.
Если мы отследим статус задачи, то WorkInfo.state вернет Cancelled. Существует также cancelAllWork
метод, который отменит все ваши задачи. Но справка предупреждает, что использовать его крайне нежелательно; он может зацепить библиотеки, которые вы используете.
Ярлык
Вы можете присвоить тег задаче, используя addTag
метод:
val workRequest: WorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
.addTag("myWorkTag")
.build()
К одной задаче можно добавить несколько тегов. В WorkInfo есть getTags
метод, который вернет все теги, назначенные этой задаче.
Присвоив один тег нескольким задачам, мы можем отменить их все с помощью cancelAllWorkByTag
метода:
WorkManager.getInstance(this).cancelAllWorkByTag("myWorkTag")
setInitialDelay
Выполнение задачи можно отложить на определенное время.
В методе setInitialDelay мы указали, что задача должна запускаться через 10 секунд после ее передачи в WorkManager.enqueue
.
Периодическая задача
Мы OneTimeWorkRequestBuilder
рассмотрели это одноразовая задача. И если вам нужно несколько исполнений через определенный промежуток времени, вы можете использовать PeriodicWorkRequestBuilder
:
val periodicWorkRequest: WorkRequest = PeriodicWorkRequestBuilder<MyWorker>(30, TimeUnit.MINUTES)
.build()
В билдере установите интервал 30 минут. Теперь задача будет выполняться с этим интервалом.
Минимальный доступный интервал составляет 15 минут. Если вы установите меньшее значение, WorkManager автоматически увеличит его до 15 минут.
WorkManager гарантирует, что задача будет запущена один раз в течение указанного интервала. И это может произойти в любой момент интервала — через 1 минуту, через 10 или через 29. С помощью параметра flex можно ограничить допустимый диапазон времени запуска.
val periodicWorkRequest: WorkRequest = PeriodicWorkRequestBuilder<MyWorker>(30, TimeUnit.MINUTES, 25, TimeUnit.MINUTES)
.build()
Помимо интервала в 30 минут дополнительно передаем флекс-билдеру параметр 25 минут. Теперь задание не будет запускаться в любой момент 30-минутного интервала, а только после 25-й минуты. Те, между 25 и 30 минут.