В этом уроке знакомимся с WorkManager.

 


Полный список уроков курса:


 

 

Немаловажная часть работы приложения - это фоновая работа. Это может быть загрузка или аплоад, сжатие или распаковка, синхронизация и т.п. Когда-то давно для фоновой работы были предназначены сервисы. Но в 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 {
           for (int i = 0; i < 1; i++) {
               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, Kotlin, RxJava, Dagger, Тестирование 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме 




Language

Социальные сети

 

Telegram канал



Android чат в Telegram



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal