WorkManager. Послідовність виконання завдань.
Буває необхідність запускати завдання в певному порядку. Наприклад, є завдання - завантажити архів із файлами, розпакувати його і якимось чином обробити файли. Це може бути виконано трьома послідовними завданнями:
- завантаження архіву
- розпакування архіву
- обробка файлів
Давайте подивимося, як можна запускати завдання послідовно.
Для початку переконаємося, що завдання, запущені звичайним шляхом, будуть виконані паралельно.
Для тестових прикладів я зробив завдання, які просто ставлять потік на паузу кодом 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 - поточну послідовність не було перервано, а нову було запущено відразу після закінчення поточної.
Акуратніше з цим режимом, оскільки помилка в поточній послідовності може призвести до того, що нова послідовність не запуститься.
У цих останніх прикладах я створював і перезапускав однакову послідовність, але поточна і нова послідовності можуть складатися з різних завдань. Головне тут - однакове ім'я послідовностей.