Предыдущая статья из этой серии: Запуск фоновых задач в Android с помощью WorkManager: часть 3
Отправка и получение данных
В этой статье мы рассмотрим, как передать данные в задачу и как получить результат.
Когда мы запускаем задачу, нам может понадобиться передать ей данные и получить результат. Давайте посмотрим, как это можно сделать.
Входные данные
Во-первых, давайте посмотрим, как передать входные данные в задачу:
val myData: Data = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.build()
val myWorkRequest: OneTimeWorkRequest = OneTimeWorkRequestBuilder<MyFirstWorker>()
.setInputData(myData)
.build()
WorkManager
.getInstance(this)
.enqueue(myWorkRequest)
Данные помещаются в Data
объект с помощью его построителя. Далее мы передаем этот объект в setInputData
метод построителя WorkRequest.
Когда задача запущена, то внутри нее (в MyFirstWorker
) мы можем получить ввод вот так:
class MyFirstWorker(context: Context, parameters: WorkerParameters): Worker(context, parameters) {
private val TAG = this.javaClass.simpleName
override fun doWork(): Result {
val valueA = inputData.getString("keyA")
val valueB = inputData.getInt("keyB", 0)
Log.d(TAG, "doWork:valueA $valueA")
Log.d(TAG, "doWork:valueB $valueB")
return Result.success()
}
}
Журналы:
2023-04-20 12:44:00 29725-29754 MyFirstWorker D doWork:valueA value1
2023-04-20 12:44:00 29725-29754 MyFirstWorker D doWork:valueB 1
Выходные данные
Чтобы задача вернула данные, вы должны передать их методу setOutputData
. Код MyFirstWorker
будет таким:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyC", "value11")
.putInt("keyD", 11)
.build()
return Result.success(output)
}
Мы можем получить этот вывод из WorkInfo
:
WorkManager
.getInstance(this)
.getWorkInfoByIdLiveData(myWorkRequest.id)
.observe(this) { info ->
when (info?.state) {
WorkInfo.State.FAILED -> {
}
WorkInfo.State.SUCCEEDED -> {
val valueC = info.outputData.getString("keyC")
val valueD = info.outputData.getInt("keyD", 0)
Log.e(TAG, "value: $valueC" )
Log.e(TAG, "value: $valueD" )
}
else -> {
}
}
}
Результат:
2023-04-21 08:33:20 32749-32749 WorkManagerActivity E value: value11
2023-04-21 08:33:20 32749-32749 WorkManagerActivity E value: 11
Объект Data, в котором хранятся данные, имеет getKeyValueMap
метод, который возвращает вам Map
всю информацию об этом файле Data
.
И Data.Builder
имеет putAll(Map<String, Object> values)
метод, в который можно передать Map
всю информацию, которая будет помещена в Data
.
Данные между задачами
Если вы создаете последовательность задач, выходные данные предыдущей задачи будут переданы в качестве входных данных для последующей задачи.
Например, запускаем последовательность из первой и второй задач:
WorkManager
.getInstance(this)
.beginWith(myWorkRequest1)
.then(myWorkRequest2)
.enqueue()
Если первая задача возвращает выходные данные следующим образом:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.build()
return Result.success(output)
}
Тогда во втором они придут на вход и мы сможем их получить обычным способом:
override fun doWork(): Result {
val valueA = inputData.getString("keyA")
val valueB = inputData.getInt("keyB", 0)
Log.d(TAG, "doWork:valueA $valueA")
Log.d(TAG, "doWork:valueB $valueB")
return Result.success()
}
Журналы:
2023-04-21 08:53:24 628-661 MySecondWorker D doWork:valueA value1
2023-04-21 08:53:24 628-661 MySecondWorker D doWork:valueB 1
Немного усложним пример:
WorkManager
.getInstance(this)
.beginWith(listOf(myWorkRequest1, myWorkRequest2))
.then(myWorkRequest3)
.enqueue()
Параллельно выполняются первая и вторая задачи, затем выполняется третья. В результате выход из первой и второй задачи попадет в третью. Посмотрим, как получится.
Пусть первая задача возвращает следующие данные:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.putString("keyC", "valueC")
.build()
return Result.success(output)
}
И второй такой:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value2")
.putInt("keyB", 2)
.putString("keyD", "valueD")
.build()
return Result.success(output)
}
Обратите внимание, я специально сделал одинаковые ключи: keyA
и keyB
для того, чтобы проверить, какие значения этих ключей придут в третью задачу — из первой задачи или из второй.
Вывожу в лог входные данные третьей задачи:
override fun doWork(): Result {
Log.d(TAG, "data " + inputData.keyValueMap)
return Result.success()
}
Результат:
2023-04-21 09:04:02 783-838 MyThirdWorker D data {keyA=value2, keyB=2, keyC=valueC, keyD=valueD}
В тех же ключах (keyA и keyB) мы видим, что данные пришли из второй задачи. Сначала я подумал, что это произошло из-за того, что вторая задача заняла немного больше времени, чем первая, и логично, что ее значения перезапишут значения из первой задачи при совпадении ключей. Но затем я снова запустил эту последовательность и получил такой результат.
2023-04-21 09:05:47 963-995 MyThirdWorker D data {keyA=value1, keyB=1, keyC=valueC, keyD=valueD}
Теперь мы видим значения первой задачи в ключах keyA
и keyB
.
Если задачи выполняются параллельно и если ключи совпадают, неизвестно, из какой задачи вы получите значение. Так что будьте осторожны здесь.
ВводСлияние
Чтобы преобразовать несколько выходов в один вход, используйте InputMerger
. Для этого существует несколько реализаций, и по умолчанию используется OverwritingInputMerger
. Мы уже видели, как это работает. Если ключ совпадает, то останется только одно значение.
Рассмотрим еще один InputMerger — ArrayCreatingInputMerger
. Если ключи совпадают, он создаст массив, в который поместит все значения этого ключа.
Укажем его для третьей задачи с помощью метода setInputMerger
:
val myWorkRequest3: OneTimeWorkRequest = OneTimeWorkRequestBuilder<MyThirdWorker>()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.build()
Теперь будет ArrayCreatingInputMerger
использоваться при объединении выходных данных предыдущих задач с входными данными третьей задачи.
Результатом его работы всегда является массив, даже если совпадений ключей не было:
override fun doWork(): Result {
val valueA = inputData.getStringArray("keyA")
val valueB = inputData.getIntArray("keyB")
val valueC = inputData.getStringArray("keyC")
val valueD = inputData.getStringArray("keyD")
Log.d(TAG, "valueA ${valueA?.toList()}")
Log.d(TAG, "valueB ${valueB?.toList()}")
Log.d(TAG, "valueC ${valueC?.toList()}")
Log.d(TAG, "valueD ${valueD?.toList()}")
return Result.success()
}
Давайте используем тот же пример, чтобы проверить это:
WorkManager
.getInstance(this)
.beginWith(listOf(myWorkRequest1, myWorkRequest2))
.then(myWorkRequest3)
.enqueue()
Первая задача вернет следующие данные:
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.putString("keyC", "valueC")
.build()
а второй такой:
val output = Data.Builder()
.putString("keyA", "value2")
.putInt("keyB", 2)
.putString("keyD", "valueD")
.build()
В третьем мы получим следующие входные данные:
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueA [value2, value1]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueB [2, 1]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueC [valueC]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueD [valueD]
Теперь при совпадении ключей данные не перезаписываются, а добавляются в массив.
Также опубликовано здесь .