Запуск фоновых задач в Android с помощью WorkManager: часть 3
5 мая 2023 г.Предыдущая статья из этой серии: Выполнение фоновых задач в Android с помощью WorkManager: часть 2
Последовательность задач
В этом руководстве рассматриваются методы выполнения задач в определенном последовательном порядке.
Например, скажем, у вас есть задача, в которой вы хотите скачать архив с файлами, распаковать его, а затем обработать файлы.
Это можно сделать с помощью трех последовательных задач:
- скачать архив
- распаковка архива
- обработка файлов
Давайте посмотрим, как мы можем запустить эти задачи последовательно
Во-первых, давайте удостоверимся, что задачи, запущенные обычным образом, будут выполняться параллельно. Запускаем сразу три задачи:
val worker1: WorkRequest = OneTimeWorkRequestBuilder<Worker1>()
.build()
val worker2: WorkRequest = OneTimeWorkRequestBuilder<Worker2>()
.build()
val worker3: WorkRequest = OneTimeWorkRequestBuilder<Worker3>()
.build()
WorkManager
.getInstance(this)
.enqueue(listOf(worker1, worker2, worker3))
Смотрим журнал:
2023-04-18 21:07:05 18984-19015 Worker2 D doWork: start
2023-04-18 21:07:05 18984-19016 Worker3 D doWork: start
2023-04-18 21:07:05 18984-19014 Worker1 D doWork: start
2023-04-18 21:07:06 18984-19014 Worker1 D doWork: end
2023-04-18 21:07:06 18984-19016 Worker3 D doWork: end
2023-04-18 21:07:06 18984-19015 Worker2 D doWork: end
Задачи запускались в 21:07:05
и выполнялись параллельно в разных потоках, и каждый выполнялся в свое время.
Мы видели параллельное исполнение. Теперь выполним их последовательно. Мы передаем первую задачу в метод beginWith
и тем самым создаем начало последовательности задач. Далее, вызывая метод then
, мы добавляем в последовательность вторую и третью задачи. И с помощью метода enqueue
мы отправляем эту последовательность на запуск.
Результат:
2023-04-18 21:28:59 20007-20036 Worker1 D doWork: start
2023-04-18 21:29:00 20007-20036 Worker1 D doWork: end
2023-04-18 21:29:00 20007-20044 Worker2 D doWork: start
2023-04-18 21:29:01 20007-20044 Worker2 D doWork: end
2023-04-18 21:29:01 20007-20045 Worker3 D doWork: start
2023-04-18 21:29:02 20007-20045 Worker3 D doWork: end
Логи показывают, что задачи выполнялись одна за другой и именно в той последовательности, которую мы указали.
Как критерии повлияют на выполнение последовательности задач?
Задача, которую нельзя запустить в данный момент, будет ждать. И, соответственно, все остальные задачи, которые стоят в последовательности после этой задачи, тоже будут ждать.
Давайте рассмотрим пример.
Пусть у второй задачи есть критерий - наличие интернета. Отключаем интернет на устройстве и запускаем последовательность. Первой задаче все равно; это работает. Приходит очередь второй задачи, а интернета нет; поэтому вторая задача откладывается, а третья задача может быть запущена только после завершения второй. Так что приходится ждать. Включаем интернет, выполняется второе задание, а после него выполняется и третье.
Если какая-либо задача в последовательности завершается со статусом FAILURE, вся цепочка будет остановлена.
Мы можем комбинировать последовательное и параллельное выполнение задач.
WorkManager
.getInstance(this)
.beginWith(listOf(worker1, worker2))
.then(listOf(worker3, worker4))
.then(worker5)
.enqueue()
Здесь мы формируем последовательность, но при этом указываем две задачи для первого (beginWith) и второго (first then) шага последовательности.
В результате задачи worker1
и worker2
будут выполняться первыми и выполняться параллельно. После этого будут выполняться worker3
и worker4
, также параллельно друг другу. И после этого - worker5
.
В логах это будет выглядеть так:
2023-04-18 21:41:31 20434-20464 Worker1 D doWork: start
2023-04-18 21:41:31 20434-20465 Worker2 D doWork: start
2023-04-18 21:41:32 20434-20464 Worker1 D doWork: end
2023-04-18 21:41:32 20434-20465 Worker2 D doWork: end
2023-04-18 21:41:32 20434-20476 Worker3 D doWork: start
2023-04-18 21:41:32 20434-20464 Worker4 D doWork: start
2023-04-18 21:41:33 20434-20476 Worker3 D doWork: end
2023-04-18 21:41:33 20434-20464 Worker4 D doWork: end
2023-04-18 21:41:33 20434-20465 Worker5 D doWork: start
2023-04-18 21:41:34 20434-20465 Worker5 D doWork: end
Первая и вторая задачи запускаются одновременно. Когда они оба завершены, начинаются третий и четвертый, также одновременно. Когда они оба выполнены, начинается пятая задача.
Рассмотрим другой случай. Предположим, нам нужно, чтобы вторая задача выполнялась после первой, а четвертая — после третьей. У нас есть две последовательности, и их можно запускать параллельно. И когда эти две последовательности выполнены, нужно приступить к пятой задаче.
Вот как это делается:
val chain12: WorkContinuation = WorkManager
.getInstance(this)
.beginWith(worker1)
.then(worker2)
val chain34: WorkContinuation = WorkManager
.getInstance(this)
.beginWith(worker3)
.then(worker4)
WorkContinuation.combine(listOf(chain12, chain34))
.then(worker5)
.enqueue()
WorkContinuation
– это последовательность задач. Мы создаем последовательность chain12
, состоящую из первой и второй задач, и последовательность chain34
, состоящую из третьей и четвертой задач. Чтобы эти последовательности выполнялись параллельно друг другу, мы передаем их методу combine
. Затем мы передаем пятую задачу методу then
, который запускается после завершения всех последовательностей из combine
.
Результат:
2023-04-19 15:58:20 1511-1534 Worker1 D doWork: start
2023-04-19 15:58:20 1511-1535 Worker3 D doWork: start
2023-04-19 15:58:21 1511-1534 Worker1 D doWork: end
2023-04-19 15:58:21 1511-1535 Worker3 D doWork: end
2023-04-19 15:58:21 1511-1571 Worker2 D doWork: start
2023-04-19 15:58:21 1511-1534 Worker4 D doWork: start
2023-04-19 15:58:22 1511-1571 Worker2 D doWork: end
2023-04-19 15:58:22 1511-1534 Worker4 D doWork: end
2023-04-19 15:58:22 1511-1571 Worker5 D doWork: start
2023-04-19 15:58:23 1511-1571 Worker5 D doWork: end
Запускаются первая и третья задачи, т.е. последовательности начинают выполняться параллельно. Когда обе последовательности выполнены, начинается пятая задача.
Уникальная работа
Мы можем сделать последовательность задач уникальной. Для этого мы запускаем последовательность с помощью метода beginUniqueWork.
private fun work() {
val worker1 = OneTimeWorkRequestBuilder<Worker1>()
.build()
val worker3 = OneTimeWorkRequestBuilder<Worker3>()
.build()
val worker5 = OneTimeWorkRequestBuilder<Worker5>()
.build()
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.REPLACE, worker1)
.then(worker3)
.then(worker5)
.enqueue()
}
findViewById<AppCompatButton>(R.id.startWork).setOnClickListener {
Log.e(TAG, "enqueue REPLACE")
work()
}
Укажите имя последовательности, режим и первую задачу последовательности.
Мы указали REPLACE в качестве режима. Это означает, что если последовательность с таким именем уже запущена, то при следующем запуске текущая последовательность будет остановлена и запущена новая.
Я добавил журналирование в вызов метода enqueue
, который запускает последовательность. Давайте посмотрим, что происходит в журналах.
2023-04-19 16:52:32 17809-17809 WorkManagerActivity3 E enqueue REPLACE
2023-04-19 16:52:33 17809-17882 Worker1 D doWork: start
2023-04-19 16:52:38 17809-17882 Worker1 D doWork: end
2023-04-19 16:52:38 17809-17914 Worker3 D doWork: start
2023-04-19 16:52:43 17809-17914 Worker3 D doWork: end
2023-04-19 16:52:43 17809-17952 Worker5 D doWork: start
2023-04-19 16:52:46 17809-17809 WorkManagerActivity3 E enqueue REPLACE
2023-04-19 16:52:46 17809-17880 Worker5 D onStopped
2023-04-19 16:52:46 17809-17882 Worker1 D doWork: start
2023-04-19 16:52:48 17809-17952 Worker5 D doWork: end
2023-04-19 16:52:51 17809-17882 Worker1 D doWork: end
2023-04-19 16:52:51 17809-17914 Worker3 D doWork: start
2023-04-19 16:52:56 17809-17914 Worker3 D doWork: end
2023-04-19 16:52:56 17809-17952 Worker5 D doWork: start
2023-04-19 16:53:01 17809-17952 Worker5 D doWork: end
16:52:32 — это первый запуск последовательности. Задачи начинают выполняться одна за другой.
16:52:46 - пока работает MyWorker5, я создаю и запускаю такую же последовательность с тем же именем - work123. Текущая последовательность останавливается и запускается новая.
Режим KEEP будет поддерживать текущую выполняемую последовательность. Новый будет проигнорирован.
Код:
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.KEEP, worker1)
.then(worker3)
.then(worker5)
.enqueue()
Журналы:
2023-04-19 17:05:17 20030-20030 WorkManagerActivity3 E enqueue KEEP
2023-04-19 17:05:18 20030-20207 Worker1 D doWork: start
2023-04-19 17:05:23 20030-20207 Worker1 D doWork: end
2023-04-19 17:05:23 20030-20227 Worker3 D doWork: start
2023-04-19 17:05:28 20030-20227 Worker3 D doWork: end
2023-04-19 17:05:28 20030-20272 Worker5 D doWork: start
2023-04-19 17:05:29 20030-20030 WorkManagerActivity3 E enqueue KEEP
2023-04-19 17:05:33 20030-20272 Worker5 D doWork: end
17:05:29 - Я попытался запустить последовательность еще раз, но был проигнорирован, так как в работе уже есть последовательность с таким названием.
Режим APPEND запустит новую последовательность после выполнения текущей.
Код:
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.APPEND, worker1)
.then(worker3)
.then(worker5)
.enqueue()
Журналы:
2023-04-19 17:10:28 21636-21636 WorkManagerActivity3 E enqueue APPEND
2023-04-19 17:10:28 21636-21730 Worker1 D doWork: start
2023-04-19 17:10:33 21636-21730 Worker1 D doWork: end
2023-04-19 17:10:33 21636-21784 Worker3 D doWork: start
2023-04-19 17:10:38 21636-21784 Worker3 D doWork: end
2023-04-19 17:10:38 21636-21836 Worker5 D doWork: start
2023-04-19 17:10:39 21636-21636 WorkManagerActivity3 E enqueue APPEND
2023-04-19 17:10:43 21636-21836 Worker5 D doWork: end
2023-04-19 17:10:43 21636-21730 Worker1 D doWork: start
2023-04-19 17:10:48 21636-21730 Worker1 D doWork: end
2023-04-19 17:10:48 21636-21784 Worker3 D doWork: start
2023-04-19 17:10:53 21636-21784 Worker3 D doWork: end
2023-04-19 17:10:53 21636-21836 Worker5 D doWork: start
2023-04-19 17:10:58 21636-21836 Worker5 D doWork: end
17:10:39 - текущая последовательность не прерывалась, а новая запускалась сразу после окончания текущей.
Будьте осторожны с этим режимом, т.к. ошибка в текущей последовательности может привести к тому, что новая последовательность не запустится.
В этих последних примерах я создал и перезапустил одну и ту же последовательность, но текущая и новая последовательности могут состоять из разных задач. Главное здесь такое же название, как и у последовательностей.
Оригинал