В этом уроке рассматриваем, как запускать задачи в определенном порядке
Полный список уроков курса:
- Урок 1. Lifecycle
- Урок 2. LiveData
- Урок 3. LiveData. Дополнительные возможности
- Урок 4. ViewModel
- Урок 5. Room. Основы
- Урок 6. Room. Entity
- Урок 7. Room. Insert, Update, Delete, Transaction
- Урок 8. Room. Query
- Урок 9. Room. RxJava
- Урок 10. Room. Запрос из нескольких таблиц. Relation
- Урок 11. Room. Type converter
- Урок 12. Room. Миграция версий базы данных
- Урок 13. Room. Тестирование
- Урок 14. Paging Library. Основы
- Урок 15. Paging Library. PagedList и DataSource. Placeholders.
- Урок 16. Paging Library. LivePagedListBuilder. BoundaryCallback.
- Урок 17. Paging Library. Виды DataSource
- Урок 18. Android Data Binding. Основы
- Урок 19. Android Data Binding. Код в layout. Доступ к View
- Урок 20. Android Data Binding. Обработка событий
- Урок 21. Android Data Binding. Observable поля. Двусторонний биндинг.
- Урок 22. Android Data Binding. Adapter. Conversion.
- Урок 23. Android Data Binding. Использование с include, ViewStub и RecyclerView.
- Урок 24. Navigation Architecture Component. Введение
- Урок 25. Navigation. Передача данных. Type-safe аргументы.
- Урок 26. Navigation. Параметры навигации
- Урок 27. Navigation. NavigationUI.
- Урок 28. Navigation. Вложенный граф. Global Action. Deep Link.
- Урок 29. WorkManager. Введение
- Урок 30. WorkManager. Критерии запуска задачи.
- Урок 31. WorkManager. Последовательность выполнения задач.
- Урок 32. WorkManager. Передача и получение данных
- Урок 33. Практика. О чем это будет.
- Урок 34. Практика. TodoApp. Список задач.
- Урок 35. Практика. TodoApp. Просмотр задачи
Бывает необходимость запускать задачи в определенном порядке. Например, есть задача - скачать архив с файлами, распаковать его и каким-то образом обработать файлы. Это может быть выполнено тремя последовательными задачами:
- загрузка архива
- распаковка архива
- обработка файлов
Давайте посмотрим, как можно запускать задачи последовательно.
Для начала убедимся, что задачи, запущенные обычным путем, будут выполнены параллельно.
Для тестовых примеров я сделал задачи, которые просто ставят поток на паузу кодом TimeUnit.SECONDS.sleep и логируют начало и конец работы. Задача MyWorker1 будет работать одну секунду, MyWorker2 - 2 секунды и т.д.
Все задачи обернуты в OneTimeWorkRequest без каких-либо критериев.
Запускаем сразу три задачи.
WorkManager.getInstance().enqueue(myWorkRequest1, myWorkRequest2, myWorkRequest3);
Смотрим лог
20:46:38.120 2737-4244 MyWorker1 start
20:46:38.124 2737-2808 MyWorker3 start
20:46:38.130 2737-4245 MyWorker2 start
20:46:39.122 2737-4244 MyWorker1 end
20:46:40.132 2737-4245 MyWorker2 end
20:46:41.125 2737-2808 MyWorker3 end
Задачи начали работу в одно время - 20:46:38, выполнялись параллельно в разных потоках и закончили каждая в свое время.
Мы увидели параллельное выполнение. Теперь давайте выполним их последовательно.
WorkManager.getInstance() .beginWith(myWorkRequest1) .then(myWorkRequest2) .then(myWorkRequest3) .enqueue();
В метод beginWith передаем первую задачу и тем самым создаем начало последовательности задач. Далее вызовами метода then добавляем к последовательности вторую и третью задачи. И методом enqueue отправляем эту последовательность на запуск.
Результат:
21:08:31.899 4518-4614 MyWorker1 start
21:08:32.901 4518-4614 MyWorker1 end
21:08:32.929 4518-4616 MyWorker2 start
21:08:34.931 4518-4616 MyWorker2 end
21:08:34.951 4518-4617 MyWorker3 start
21:08:37.952 4518-4617 MyWorker3 end
По логам видно, что задачи выполнялись друг за другом и именно в той последовательности, что мы указали.
Как критерии повлияют на выполнение последовательности задач? Задача, которая не может сейчас быть запущена, будет ждать. И, соответственно, все остальные задачи, которые в последовательности находятся после этой задачи, также будут ждать.
Рассмотрим на примере. Пусть у второй задачи есть критерий - наличие интернет. Выключаем инет на девайсе и стартуем последовательность. Первой задаче все равно, она выполняется. Настает очередь второй задачи, но инета нет, поэтому вторая задача ставится в ожидание. А третья задача может быть запущена только после завершении второй. Поэтому ей приходится ждать. Включаем инет, вторая задача выполняется, а за ней выполняется третья.
Если какая-то задача в последовательности завершится статусом FAILURE, то вся цепочка будет остановлена.
Мы можем комбинировать последовательное и параллельное выполнение задач.
WorkManager.getInstance() .beginWith(myWorkRequest1, myWorkRequest2) .then(myWorkRequest3, myWorkRequest4) .then(myWorkRequest5) .enqueue();
Мы здесь формируем последовательность, но при этом указываем по две задачи для первого (beginWith) и второго (первый then) шага последовательности.
В результате сначала будут выполнены задачи myWorkRequest1 и myWorkRequest2, причем они будут выполнены параллельно. После этого будут выполнены myWorkRequest3 и myWorkRequest4, также параллельно друг другу. А после этого - myWorkRequest5.
В логах это будет выглядеть так:
21:35:14.851 5379-5420 MyWorker1 start
21:35:14.853 5379-5421 MyWorker2 start
21:35:15.852 5379-5420 MyWorker1 end
21:35:16.854 5379-5421 MyWorker2 end
21:35:16.882 5379-5425 MyWorker3 start
21:35:16.884 5379-5420 MyWorker4 start
21:35:19.882 5379-5425 MyWorker3 end
21:35:20.885 5379-5420 MyWorker4 end
21:35:20.910 5379-5421 MyWorker5 start
21:35:25.911 5379-5421 MyWorker5 end
Первая и вторая задача стартуют одновременно. Когда они обе завершены, стартуют третья и четвертая, также одновременно. Когда они обе завершены, стартует пятая задача.
Рассмотрим другой случай. Допустим, нам нужно, чтобы вторая задача выполнилась после первой, а четвертая после третьей. Т.е. у нас есть две последовательности, и они могут быть запущены параллельно. А когда две эти последовательности будут выполнены, необходимо запустить пятую задачу.
Это делается так:
WorkContinuation chain12 = WorkManager.getInstance() .beginWith(myWorkRequest1) .then(myWorkRequest2); WorkContinuation chain34 = WorkManager.getInstance() .beginWith(myWorkRequest3) .then(myWorkRequest4); WorkContinuation.combine(chain12, chain34) .then(myWorkRequest5) .enqueue();
WorkContinuation - это последовательность задач. Мы создаем последовательность chain12, состоящую из первой и второй задач, и последовательность chain34, состоящую из третьей и четвертой задач. Чтобы эти последовательности были запущены параллельно друг другу, мы передаем их в метод combine. Затем в метод then передаем пятую задачу, которая стартует после того, как будут выполнены все последовательности из combine.
Результат:
21:45:50.470 5578-5623 MyWorker1 start
21:45:50.470 5578-5624 MyWorker3 start
21:45:51.471 5578-5623 MyWorker1 end
21:45:51.500 5578-5625 MyWorker2 start
21:45:53.470 5578-5624 MyWorker3 end
21:45:53.486 5578-5623 MyWorker4 start
21:45:53.502 5578-5625 MyWorker2 end
21:45:57.486 5578-5623 MyWorker4 end
21:45:57.534 5578-5625 MyWorker5 start
21:46:02.535 5578-5625 MyWorker5 end
Стартуют первая и третья задачи, т.е. последовательности начинают работать параллельно. Когда обе последовательности выполнены, стартует пятая задача.
Unique work
Мы можем сделать последовательность задач уникальной. Для этого начинаем последовательность методом beginUniqueWork.
WorkManager.getInstance() .beginUniqueWork("work123", ExistingWorkPolicy.REPLACE, myWorkRequest1) .then(myWorkRequest3) .then(myWorkRequest5) .enqueue();
Указываем имя последовательности, режим и первую задачу последовательности.
В качестве режима мы указали REPLACE. Это означает, что, если последовательность с таким именем уже находится в работе, то еще один запуск приведет к тому, что текущая выполняемая последовательность будет остановлена, а новая запущена.
Я добавил логирование вызова метода enqueue, который запускает последовательность. Давайте посмотрим в логах, что происходит.
22:17:21.443 6261-6261 enqueue, REPLACE
22:17:21.502 6261-6301 MyWorker1 start
22:17:22.504 6261-6301 MyWorker1 end
22:17:22.531 6261-6303 MyWorker3 start
22:17:25.532 6261-6303 MyWorker3 end
22:17:25.557 6261-6304 MyWorker5 start
22:17:27.139 6261-6261 enqueue, REPLACE
22:17:27.144 6261-6283 MyWorker5 onStopped
22:17:27.197 6261-6301 MyWorker1 start
22:17:28.198 6261-6301 MyWorker1 end
22:17:28.223 6261-6301 MyWorker3 start
22:17:30.557 6261-6304 MyWorker5 end
22:17:31.225 6261-6301 MyWorker3 end
22:17:31.243 6261-6303 MyWorker5 start
22:17:36.245 6261-6303 MyWorker5 end
22:17:21 - это первый запуск последовательности. Задачи начинают выполняться одна за другой.
22:17:27 - во время работы MyWorker5 я создаю и стартую такую же последовательность с тем же именем - work123. Текущая выполняемая последовательность останавливается, и новая начинается.
Режим KEEP оставит в работе текущую выполняемую последовательность. А новая будет проигнорирована.
Код:
WorkManager.getInstance() .beginUniqueWork("work123", ExistingWorkPolicy.KEEP, myWorkRequest1) .then(myWorkRequest3) .then(myWorkRequest5) .enqueue();
Логи:
22:18:22.215 6351-6351 enqueue, KEEP
22:18:22.282 6351-6392 MyWorker1 start
22:18:23.284 6351-6392 MyWorker1 end
22:18:23.309 6351-6393 MyWorker3 start
22:18:26.311 6351-6393 MyWorker3 end
22:18:26.334 6351-6394 MyWorker5 start
22:18:27.837 6351-6351 enqueue, KEEP
22:18:31.336 6351-6394 MyWorker5 end
22:18:27 - я снова попытался запустить последовательность, но был проигнорирован, т.к. в работе уже есть последовательность с таким именем.
Режим APPEND запустит новую последовательность после выполнения текущей.
Код:
WorkManager.getInstance() .beginUniqueWork("work123", ExistingWorkPolicy.APPEND, myWorkRequest1) .then(myWorkRequest3) .then(myWorkRequest5) .enqueue();
Логи:
22:19:01.376 6450-6450 enqueue, APPEND
22:19:01.440 6450-6478 MyWorker1 start
22:19:02.441 6450-6478 MyWorker1 end
22:19:02.464 6450-6479 MyWorker3 start
22:19:05.465 6450-6479 MyWorker3 end
22:19:05.496 6450-6480 MyWorker5 start
22:19:06.911 6450-6450 enqueue, APPEND
22:19:10.496 6450-6480 MyWorker5 end
22:19:10.517 6450-6480 MyWorker1 start
22:19:11.518 6450-6480 MyWorker1 end
22:19:11.541 6450-6479 MyWorker3 start
22:19:14.542 6450-6479 MyWorker3 end
22:19:14.561 6450-6478 MyWorker5 start
22:19:19.562 6450-6478 MyWorker5 end
22:19:06 - текущая последовательность не была прервана, а новая была запущена сразу после окончания текущей.
Аккуратнее с этим режимом, т.к. ошибка в текущей последовательности может привести к тому, что новая последовательность не запустится.
В этих последних примерах я создавал и перезапускал одинаковую последовательность, но текущая и новая последовательности могут состоять из разных задач. Главное здесь - одинаковое имя последовательностей.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня