Предыдущая статья из этой серии:Запуск фоновых задач в 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 — текущая последовательность не прерывалась, а новая запускалась сразу после окончания текущей.
Будьте осторожны с этим режимом, т.к. ошибка в текущей последовательности может привести к тому, что новая последовательность не запустится.
В этих последних примерах я создал и перезапустил одну и ту же последовательность, но текущая и новая последовательности могут состоять из разных задач. Главное здесь такое же название, как и последовательности.