В этом уроке знакомимся с WorkManager.
Полный список уроков курса:
- Урок 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. Просмотр задачи
Немаловажная часть работы приложения - это фоновая работа. Это может быть загрузка или аплоад, сжатие или распаковка, синхронизация и т.п. Когда-то давно для фоновой работы были предназначены сервисы. Но в Android 8 их очень сильно ограничили: если приложение не активно, то и сервис будет остановлен через какое-то время. Да и еще задолго до Android 8 разработчики начали использовать такие инструменты как JobScheduler или Firebase JobDispatcher для запуска фоновых задач.
WorkManager - новый инструмент. Он позволяет запускать фоновые задачи последовательно или параллельно, передавать в них данные, получать из них результат, отслеживать статус выполнения и запускать только при соблюдении заданных условий.
При этом он очень простой в использовании. Я рассчитываю, что мне хватит 4 небольших урока, чтобы рассмотреть все его возможности.
Задача
Давайте создадим и запустим фоновую задачу.
Добавьте в dependencies
implementation "android.arch.work:work-runtime:1.0.0-alpha02"
Создаем класс, наследующий класс Worker:
public class MyWorker extends Worker { static final String TAG = "workmng"; @NonNull @Override public WorkerResult doWork() { Log.d(TAG, "doWork: start"); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG, "doWork: end"); return WorkerResult.SUCCESS; } }
В метод doWork нам предлагается поместить код, который будет выполнен. Я здесь просто ставлю паузу в 10 секунд и возвращаю результат SUCCESS, означающий, что все прошло успешно. Нам не надо заморачиваться с потоками, т.к. код будет выполнен не в UI потоке.
Задача готова. Теперь нам нужно MyWorker обернуть в WorkRequest:
OneTimeWorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWorker.class).build();
WorkRequest позволяет нам задать условия запуска и входные параметры к задаче. Пока что мы ничего не задаем, а просто создаем OneTimeWorkRequest, которому говорим, что запускать надо будет задачу MyWorker.
OneTimeWorkRequest не зря имеет такое название. Эта задача будет выполнена один раз. Есть еще PeriodicWorkRequest, но о нем чуть позже.
Теперь можно запускать задачу:
WorkManager.getInstance().enqueue(myWorkRequest);
Берем WorkManager и в его метод enqueue передаем WorkRequest. После этого задача будет запущена.
Смотрим лог:
20:37:36.567 5369-5444 doWork: start
20:37:46.568 5369-5444 doWork: end
Видно, что задача выполнялась 10 секунд, и код выполнялся не в UI потоке.
Статус задачи
WorkManager предоставляет возможность отслеживать статус выполнения задачи. Например в Activity пишем:
WorkManager.getInstance().getStatusById(myWorkRequest.getId()).observe(this, new Observer<WorkStatus>() { @Override public void onChanged(@Nullable WorkStatus workStatus) { Log.d(TAG, "onChanged: " + workStatus.getState()); } });
В метод getStatusById необходимо передать ID задачи, который может быть получен методом WorkRequest.getId. В результате мы получаем LiveData, подписываемся на него и в метод onChanged нам будут приходить все изменения статуса нашей задачи. Методом WorkStatus.getState будем получать текущее состояние.
Запускаем
20:52:54.189 6060-6060 onChanged: ENQUEUED
20:52:54.199 6060-6087 doWork: start
20:52:54.203 6060-6060 onChanged: RUNNING
20:53:04.200 6060-6087 doWork: end
20:53:04.211 6060-6060 onChanged: SUCCEEDED
Сразу после вызова метода enqueue задача находится в статусе ENQUEUED. Затем WorkManager определяет, что задачу можно запускать и выполняет наш код. В этот момент статус меняется на RUNNING. После выполнения статус будет SUCCEEDED, т.к. мы вернули такой статус в методе doWork.
Статус нам приходит в UI потоке.
Теперь еще раз запустим задачу и закроем приложение:
20:58:19.402 doWork: start
20:58:19.424 onChanged: ENQUEUED
20:58:19.462 onChanged: RUNNING
20:58:29.403 doWork: end
Обратите внимание, задача завершилась, а статус SUCCEEDED не пришел. Почему? Потому что, закрыв Activity мы всего лишь отписались от LiveData, который передавал нам статусы задачи. Но сама задача никуда не делась. Она никак не зависит от приложения и будет выполняться, даже если приложение закрыто.
Результат
Мы в нашей задаче возвращали статус WorkerResult.SUCCESS, тем самым сообщая, что все ок. Есть еще два варианта:
FAILURE - в этом случае после завершения задачи workStatus.getState вернет FAILED. Для нас это сигнал, что задача не была выполнена.
RETRY - а этот результат является сигналом для WorkManager, что задачу надо повторить. В этом случае workStatus.getState вернет нам статус ENQUEUED - т.е. задача снова запланирована.
Я протестировал на эмуляторе поведение при RETRY: первый раз задача была перезапущена примерно через одну минуту после предыдущего завершения. С каждым последующим перезапуском интервал увеличивался:
21:10:22.637 doWork: start
21:10:32.638 doWork: end
21:11:32.655 doWork: start
21:11:42.657 doWork: end
21:14:07.538 doWork: start
21:14:17.543 doWork: end
21:18:17.561 doWork: start
21:18:27.602 doWork: end
21:26:27.618 doWork: start
21:26:37.653 doWork: end
Отмена задачи
Мы можем отменить задачу методом cancelWorkById, передав ID задачи
WorkManager.getInstance().cancelWorkById(myWorkRequest.getId());
При этом в классе MyWorker будет вызван метод onStopped (если вы его реализовали). Также в классе MyWorker мы всегда можем использовать boolean метод isStopped для проверки того, что задача была отменена.
Если отслеживаем статус задачи, то WorkStatus.getState вернет Cancelled.
Также есть метод cancelAllWork, который отменит все ваши задачи. Но хелп предупреждает, что он крайне нежелателен к использованию, т.к. может зацепить работу библиотек, которые вы используете.
Tag
Задаче можно присвоить тег методом addTag:
OneTimeWorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .addTag("mytag") .build();
Одной задаче можно добавлять несколько тегов.
У WorkStatus есть метод getTags, который вернет все теги, которые присвоены этой задаче.
Присвоив один тег нескольким задачам, мы можем всех их отменить методом cancelAllWorkByTag:
WorkManager.getInstance().cancelAllWorkByTag("mytag");
setInitialDelay
Выполнение задачи можно отложить на указанное время
OneTimeWorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInitialDelay(10, TimeUnit.SECONDS) .build();
В методе setInitialDelay мы указали, что задачу следует запустить через 10 секунд после передачи ее в WorkManager.enqueue
Периодическая задача
Рассмотренный нами OneTimeWorkRequest - это разовая задача. А если нужно многократное выполнение через определенный период времени, то можно использовать PeriodicWorkRequest:
PeriodicWorkRequest myWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 30, TimeUnit.MINUTES) .build();
В билдере задаем интервал в 30 минут. Теперь задача будет выполняться с этим интервалом.
Минимально доступный интервал - 15 минут. Если поставите меньше, WorkManager сам повысит до 15 минут.
WorkManager гарантирует, что задача будет запущена один раз в течение указанного интервала. И это может случиться в любой момент интервала - через 1 минуту, через 10 или через 29.
С помощью параметра flex можно ограничить разрешенный диапазон времени запуска.
PeriodicWorkRequest myWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 30, TimeUnit.MINUTES, 25, TimeUnit.MINUTES) .build();
Кроме интервала в 30 минут дополнительно передаем в билдер flex параметр 25 минут. Теперь задача будет запущена не в любой момент 30-минутного интервала, а только после 25-й минуты. Т.е. между 25 и 30 минутами.
Context
Чтобы получить Context в Worker классе, используйте метод getApplicationContext.
Перезагрузка
Что происходит с запланированными задачами при перезагрузке устройства? Я протестил этот кейс на эмуляторе и выяснил, что все задачи сохраняются. Т.е. OneTimeWorkRequest c отложенным запуском, OneTimeWorkRequest с результатом RETRY, PeriodicWorkRequest - все эти задачи будут снова запущены после перезагрузки устройства.
Поэтому действуйте обдуманно и храните где-то у себя ID или тэг задачи, чтобы вы могли ее отменить, если она вам больше не нужна.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня