我在我的应用程序中使用 WorkManager 来延迟工作。 总工作分为多个链接的工作人员,我无法向用户显示工作人员的进度(使用进度条)。
我尝试创建一个标签并将其添加到不同的工作人员中,并在工作人员内部通过该标签更新进度,但当我调试时,我总是得到进度为“0”。
我注意到的另一件事是,每次我开始工作时,workManager 的工作信息列表都会变大(即使工作人员完成了工作)。
这是我的代码:
//inside view model
private val workManager = WorkManager.getInstance(appContext)
internal val progressWorkInfoItems: LiveData<List<WorkInfo>>
init
{
progressWorkInfoItems = workManager.getWorkInfosByTagLiveData(TAG_SAVING_PROGRESS)
}
companion object
{
const val TAG_SAVING_PROGRESS = "saving_progress_tag"
}
//inside a method
var workContinuation = workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
val secondWorkRequest = OneTimeWorkRequestBuilder<SecondWorker>()
secondWorkRequest.addTag(TAG_SAVING_PROGRESS)
secondWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(secondWorkRequest.build())
val thirdWorkRequest = OneTimeWorkRequestBuilder<ThirdWorker>()
thirdWorkRequest.addTag(TAG_SAVING_PROGRESS)
thirdWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(thirdWorkRequest.build())
workContinuation.enqueue()
//inside the Activity
viewModel.progressWorkInfoItems.observe(this, observeProgress())
private fun observeProgress(): Observer<List<WorkInfo>>
{
return Observer { listOfWorkInfo ->
if (listOfWorkInfo.isNullOrEmpty()) { return@Observer }
listOfWorkInfo.forEach { workInfo ->
if (WorkInfo.State.RUNNING == workInfo.state)
{
val progress = workInfo.progress.getFloat(TAG_SAVING_PROGRESS, 0f)
progress_bar?.progress = progress
}
}
}
}
//inside the worker
override suspend fun doWork(): Result = withContext(Dispatchers.IO)
{
setProgress(workDataOf(TAG_SAVING_PROGRESS to 10f))
...
...
Result.success()
}
根据我的分析,我发现可能有两个原因导致你总是得到
0
setProgress
设置在工作人员 Result.success()
中的 doWork()
之前,然后它就会丢失,并且您永远不会在侦听器中获得该值。这可能是因为工作人员现在的状态是 SUCCEEDED
我们看一下下面的代码
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
setProgressAsync(Data.Builder().putInt("progress",10).build())
for (i in 1..5) {
SystemClock.sleep(1000)
}
setProgressAsync(Data.Builder().putInt("progress",50).build())
SystemClock.sleep(1000)
return Result.success()
}
}
在上面的代码中
50
10
0
此分析基于WorkManager 2.4.0版本
因此我发现以下方式是更好且始终可靠的方式来显示您的连锁工作中各个工人的进度。
我有两个工作人员需要一个接一个地运行。如果第一个工作完成,那么工作就完成了 50%,当第二个工作完成时,就完成了 100%。
两名工人
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 1..5) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",50).build())
}
}
class Worker2(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 5..10) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",100).build())
}
}
活动内部
workManager = WorkManager.getInstance(this)
workRequest1 = OneTimeWorkRequest.Builder(Worker1::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
workRequest2 = OneTimeWorkRequest.Builder(Worker2::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
findViewById<Button>(R.id.btn).setOnClickListener(View.OnClickListener { view ->
workManager?.
beginUniqueWork(TAG_SAVING_PROGRESS,ExistingWorkPolicy.REPLACE,workRequest1)
?.then(workRequest2)
?.enqueue()
})
progressBar = findViewById(R.id.progressBar)
workManager?.getWorkInfoByIdLiveData(workRequest1.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
workManager?.getWorkInfoByIdLiveData(workRequest2.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
即使工作人员完成了工作,每次工作开始时,workManager 的工作信息列表也会变得越来越大,这是因为
workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
相反,需要使用
workManager?.beginUniqueWork(TAG_SAVING_PROGRESS, ExistingWorkPolicy.REPLACE,OneTimeWorkRequest.from(firstWorker::class.java))
您可以在这里阅读更多相关信息
setProgress
方法是观察单个Worker的中间进度(如指南中所述):
只有在 ListenableWorker 运行时才能观察和更新进度信息。
因此,进度信息仅在 Worker 处于活动状态之前可用(例如,它不处于像
SUCCEEDED
、FAILED
和 CANCELLED
这样的最终状态)。 本 WorkManager 指南涵盖了 Worker 的状态。
我的建议是使用 Worker 的唯一 ID 来识别链中哪个 Worker 尚未处于终止状态。您可以使用
WorkRequest
的 getId
方法来检索其唯一 ID。
根据 @Akhil Nair 的建议,我使用
Flows
开发了类似的解决方案。但是,我遇到了一个问题,即我没有收到 CoroutineWorker
发出的最新值。
我在
setProgress
中使用 CoroutineWorker
进行实时更新,因为我的用例涉及 HTTP 分块请求。但是,最后一次 setProgress
调用距离 Result.success
太近,导致未按预期收到最终进度值。
为了解决这个问题,我决定将最终进度值与
Result.success
一起返回。这确保了最后的进度更新能够可靠地传递,即使它是在工作线程完成之前发出的。
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
// previous code
// result is a Flow<String?> that gets data from a REST API
val finalJson = result
.filterNotNull()
.onEach { jsonChunk ->
setProgress(workDataOf(AIWorkerConstants.KEY_CHUNK_RESPONSE to jsonChunk))
}
.lastOrNull()
return@withContext Result.success(
workDataOf(AIWorkerConstants.KEY_CHUNK_RESPONSE to finalJson)
)
} catch (e: Exception) {
e.printStackTrace()
return@withContext Result.failure()
}
}
override fun observeAIRoutineResponse(): Flow<AIRoutineResponse> {
return WorkManager
.getInstance(context)
.getWorkInfoByIdFlow(requestID)
.buffer(Channel.UNLIMITED)
.mapNotNull { workInfo ->
if (workInfo == null) return@mapNotNull cachedResponse
val progressChunk = workInfo.progress.getString(AIWorkerConstants.KEY_CHUNK_RESPONSE)
val outputChunk = workInfo.outputData.getString(AIWorkerConstants.KEY_CHUNK_RESPONSE)
val chunk = progressChunk.takeIf { !it.isNullOrEmpty() }
?: outputChunk.takeIf { !it.isNullOrEmpty() }
if (!chunk.isNullOrEmpty()) {
val newResponse = Json.decodeFromString<AIResponseDto>(chunk).toDomain()
cachedResponse = newResponse
newResponse
} else {
cachedResponse
}
}
.catch { e ->
Timber.e(e, "Error observing AI routine response")
cachedResponse?.let { emit(it) }
}
.filterNotNull()
}