В этом уроке рассмотрим как передать данные в задачу и как получить результат

 


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


 

 

Когда мы запускаем задачу, нам может понадобиться передать в нее данные и получить обратно результат. Давайте посмотрим, как это можно сделать.

 

 

 

Входные данные

Сначала рассмотрим как передать в задачу входные данные:

Data myData = new Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .build();

OneTimeWorkRequest myWorkRequest1 = new OneTimeWorkRequest.Builder(MyWorker1.class)
       .setInputData(myData)
       .build();

Данные помещаем в объект Data с помощью его билдера. Далее этот объект передаем в метод setInputData билдера WorkRequest.

Когда задача будет запущена, то внутри ее (в MyWorker1.java) мы можем получить эти входные данные так:

String valueA = getInputData().getString("keyA", "");
int valueB = getInputData().getInt("keyB", 0);

 

 



Выходные данные

Чтобы задача вернула данные, необходимо передать их в метод setOutputData. Код в MyWorker1.java будет следующим:

Data output = new Data.Builder()
       .putString("keyC", "value11")
       .putInt("keyD", 11)
       .build();
setOutputData(output);

 

Эти выходные данные мы сможем достать из WorkStatus

workStatus.getOutputData().getString("keyC", "")

 

У объекта Data, который хранит данные, есть метод getKeyValueMap, который вернет вам immutable Map, содержащий все данные этого Data.

А у Data.Builder есть метод putAll(Map<String, Object> values), в который вы можете передать Map, все данные из которого будут помещены в Data.

 

 

 

Данные между задачами

Если вы создаете последовательность задач, то выходные данные предыдущей задачи будут передаваться как входные в последующую задачу.

Например, запускаем последовательность из первой и второй задач

WorkManager.getInstance()
       .beginWith(myWorkRequest1)
       .then(myWorkRequest2)
       .enqueue();

  

Если первая задача возвращает такие выходные данные:

Data output = new Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .build();
setOutputData(output);

  

То во второй они придут, как входные и мы можем получить их обычным путем

String valueA = getInputData().getString("keyA", "");
int valueB = getInputData().getInt("keyB", 0);

 

 

Чуть усложним пример:

WorkManager.getInstance()
       .beginWith(myWorkRequest1, myWorkRequest2)
       .then(myWorkRequest3)
       .enqueue();

Первая и вторая задачи выполняются параллельно, затем выполняется третья. В результате выходные данные из первой и второй задач попадут в третью. Давайте посмотрим, как это получится.

 

Пусть первая задача возвращает такие данные:

Data output = new Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .putString("keyC", "valueC")
       .build();
setOutputData(output);

 

А вторая - такие

Data output = new Data.Builder()
       .putString("keyA", "value2")
       .putInt("keyB", 2)
       .putString("keyD", "valueD")
       .build();
setOutputData(output);

Обратите внимание, я специально сделал одинаковые ключи: keyA и keyB, чтобы проверить, какие значения этих ключей придут в третью задачу - из первой задачи или из второй.

 

Вывожу в лог входные данные третьей задачи:

Log.d(TAG, "work3, data " + getInputData().getKeyValueMap());

Результат:

work3, data {keyA=value2, keyB=2, keyC=valueC, keyD=valueD}

В одинаковых ключах (keyA и keyB) мы видим, что пришли данные из второй задачи. Поначалу я решил, что так произошло, потому что вторая задача выполняется чуть дольше первой, и логично, что ее значения просто перезатерли значения из первой задачи при совпадении ключей. Но потом я снова запустил эту последовательность и получил такой результат.

work3, data {keyA=value1, keyB=1, keyC=valueC, keyD=valueD}

Теперь мы видим значения первой задачи в ключах keyA и keyB.

 

Т.е. если задачи выполняются параллельно, то при совпадении ключей неизвестно, из какой именно задачи вы получите значение. Поэтому тут будьте аккуратнее.

 

 

 

InputMerger

Чтобы преобразовать несколько выходных результатов в один входной, используется InputMerger. Существует несколько его реализаций, по умолчанию используется OverwritingInputMerger. Мы уже посмотрели, как он работает. Если ключ совпадает, то останется только одно значение.

 

Рассмотрим еще один InputMerger - ArrayCreatingInputMerger. Он при совпадении ключей создаст массив, в который поместит все значения этого ключа.

Давайте для третьей задачи укажем его методом setInputMerger:

OneTimeWorkRequest myWorkRequest3 = new OneTimeWorkRequest.Builder(MyWorker3.class)
       .setInputMerger(ArrayCreatingInputMerger.class)
       .build();

Теперь при слиянии выходных данных из предыдущих задач в входные данные третьей задачи будет использоваться ArrayCreatingInputMerger.

Результат его работы - это всегда массив, даже если не было совпадений ключей

String[] valueA = getInputData().getStringArray("keyA");
int[] valueB = getInputData().getIntArray("keyB");
String[] valueC = getInputData().getStringArray("keyC");
String[] valueD = getInputData().getStringArray("keyD");

 

Для проверки используем тот же пример:

WorkManager.getInstance()
       .beginWith(myWorkRequest1, myWorkRequest2)
       .then(myWorkRequest3)
       .enqueue();

Первая и вторая задача выполняются параллельно и их выходные данные будут формировать входные данные для третьей задачи

 

Первая задача вернет такие данные:

Data output = new Data.Builder()
       .putString("keyA", "value1")
       .putInt("keyB", 1)
       .putString("keyC", "valueC")
       .build();
setOutputData(output);

 

а вторая - такие

Data output = new Data.Builder()
       .putString("keyA", "value2")
       .putInt("keyB", 2)
       .putString("keyD", "valueD")
       .build();
setOutputData(output);

 

В третьей мы получим такие входные данные:

valueA = [value1, value2]
valueB = [1, 2]
valueC = [valueC]
valueD = [valueD]

Теперь при совпадении ключей данные не перезатираются, а складываются в массив.

 

 

 

Custom merger

При необходимости мы можем написать свой InputMerger. Для этого надо просто наследовать класс InputMerger и реализовать его метод:

Data merge(@NonNull List<Data> inputs)

На вход мы получаем несколько выходных данных из предыдущих задач, и от нас требуется смержить их в входные данные для следующей задачи.

Далее остается только передать свой Merger в setInputMerger.

 

Вот пример своего Merger:

public class MyMerger extends InputMerger {

    @Override
    public @NonNull
    Data merge(@NonNull List<Data> inputs) {
        Data.Builder output = new Data.Builder();
        Map<String, Object> mergedValues = new HashMap<>();

        for (Data input : inputs) {
            mergedValues.putAll(input.getKeyValueMap());
        }

        output.putAll(mergedValues);
        output.putInt("input_data_count", inputs.size() - 1);
        return output.build();
    }

}

 

Код почти полностью повторяет OverwritingInputMerger.

Создаем Map и в цикле складываем в него данные из всех пришедших Data. При совпадении ключей, значения будут перезаписаны. Далее этот Map передаем в билдер output. И от себя добавляем ключ input_data_count, в который помещаем кол-во пришедших нам объектов Data. Тем самым входные данные следующей задачи будут содержать кол-во параллельно выполненных предыдущих задач. 

Минус 1 нужен, потому, что список, который идет на вход методу merge, содержит Data, не только пришедшие из предыдущих задач, но и Data, заданный в билдере WorkRequest (метод setInputData) задачи, которая использует этот Merger. Даже если мы явно его не задавали, он существует и придет пустой.

 

Готовим список параллельных задач:

List<OneTimeWorkRequest> workRequests = new LinkedList<>();
workRequests.add(myWorkRequest1);
workRequests.add(myWorkRequest2);
workRequests.add(myWorkRequest3);

 

Задаче, которая будет получать результаты, задаем MyMerger:

OneTimeWorkRequest myWorkRequest4 = new OneTimeWorkRequest.Builder(MyWorker4.class)
        .setInputMerger(MyMerger.class)
        .build();

 

Запускаем это все:

WorkManager.getInstance()
        .beginWith(workRequests)
        .then(myWorkRequest4)
        .enqueue();

 

И в MyWorker4 получаем смерженные данные и count=3.

 

 

 

Ну и напоследок опишу еще один случай:

WorkManager.getInstance()
       .beginWith(myWorkRequest1, myWorkRequest2)
       .then(myWorkRequest3, myWorkRequest4)
       .enqueue();

После первой и второй задач запускаем параллельно третью и четвертую. В этом случае и в третью и в четвертую задачу придут входные данные, полученные из первой и второй задач.


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование 

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

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




Language

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

 

Telegram канал



Android чат в Telegram



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



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

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal