17、任务进度更新:setProgress()方法、观察进度(WorkInfo.getProgress)、在UI中展示进度条、进度更新的最佳实践

大家好,我是你们的老朋友。今天我们来聊聊WorkManager里一个非常实用的功能——任务进度更新。

说实话,我在早期做后台任务时,经常遇到一个尴尬的场景:用户点击了「开始下载」,然后界面就卡在那里一动不动。用户不知道任务是在进行中,还是已经卡死了。这种体验,说白了就是「黑盒操作」,用户心里没底。

后来我引入了WorkManager的进度更新机制,才真正解决了这个问题。今天我就把这块内容掰开揉碎了讲给你听。

17.1 setProgress()方法:给任务装上「仪表盘」

先说说核心方法——setProgress()。这个方法的作用很简单:在Worker执行过程中,向WorkManager报告当前的进度。

它的签名是这样的:

// 在Worker内部调用
setProgress(workDataOf(
    "progress" to 0.5f,
    "status" to "正在下载..."
))

你可能会问:「为什么不用LiveData或者Flow?」嗯,这里有个设计上的考量。WorkManager的进度数据是持久化的,即使App进程被杀掉,进度信息依然保存在数据库中。等App重新启动,你依然能拿到之前的进度。这一点,普通的LiveData做不到。

我在项目中遇到过这样一个场景:用户下载一个大文件,中途接了个电话,App被系统回收了。等用户再切回来,进度条居然从0%重新开始。用户当场就炸了。后来改用setProgress(),这个问题就彻底解决了。

核心要点:setProgress() 传入的是一个 Data 对象,你可以自定义任意字段。但要注意,Data 对象的大小有限制,最大 10KB。别往里塞大对象。

17.2 观察进度:WorkInfo.getProgress 的正确打开方式

Worker那边报告了进度,UI这边怎么接收呢?答案是——通过WorkInfo

WorkManager提供了两种观察方式:

  • LiveData:适合传统的Android架构
  • Kotlin Flow:适合协程风格的代码

先看LiveData的用法:

val workManager = WorkManager.getInstance(context)
workManager.getWorkInfoByIdLiveData(workRequest.id)
    .observe(viewLifecycleOwner) { workInfo ->
        if (workInfo != null) {
            val progress = workInfo.progress
            val currentProgress = progress.getFloat("progress", 0f)
            val status = progress.getString("status", "")
            // 更新UI
        }
    }

再看Flow的版本,我个人更推荐这个:

workManager.getWorkInfoByIdFlow(workRequest.id)
    .collect { workInfo ->
        val progress = workInfo.progress
        // 处理进度数据
    }

这里有个坑,我曾经踩过:workInfo.progress返回的是一个Data对象,不是直接的数字。你需要用getFloat()getString()这些方法去提取。如果你忘了做空判断,直接调用getFloat("progress", 0f)的默认值参数,那就会拿到0。嗯,这会导致进度条一直停在0%,用户会以为任务没开始。

避坑指南:我曾经在项目里犯过一个低级错误——在Worker中调用了setProgress(),但UI侧忘记观察WorkInfo。结果进度数据白白浪费了。记得两边都要写代码。

17.3 在UI中展示进度条:从数据到视觉

拿到进度数据后,展示到UI上就简单了。我一般用ProgressBar或者MaterialProgressBar

一个典型的实现是这样的:

// 在Fragment或Activity中
private fun bindProgress(workInfo: WorkInfo) {
    val progress = workInfo.progress
    val currentProgress = progress.getFloat("progress", 0f)
    val status = progress.getString("status", "")
    
    binding.progressBar.progress = (currentProgress * 100).toInt()
    binding.statusText.text = status
    
    // 任务完成时隐藏进度条
    if (workInfo.state == WorkInfo.State.SUCCEEDED) {
        binding.progressBar.visibility = View.GONE
        binding.statusText.text = "任务完成"
    }
}

你想想看,用户看到进度条在动,心里就有底了。哪怕任务执行了30秒,用户也愿意等。这就是「感知性能」的重要性。

小技巧:如果任务执行时间很短(比如小于1秒),我建议不要显示进度条。否则进度条一闪而过,反而让用户觉得奇怪。可以加一个阈值判断:只有预估执行时间超过2秒的任务,才展示进度。

17.4 进度更新的最佳实践:我踩过的坑和总结的经验

这部分是我最想跟你分享的。毕竟光知道API怎么用还不够,真正落地时会有各种细节问题。

17.4.1 更新频率控制

我在项目中遇到过一个问题:Worker里每处理一条数据就调用一次setProgress()。结果呢?UI线程被频繁刷新,界面卡顿。更糟糕的是,WorkManager的数据库写入操作也很频繁,反而拖慢了任务本身。

解决方案很简单——控制更新频率:

// 每处理10条数据,或者每过500ms,才更新一次进度
var lastUpdateTime = 0L
var processedCount = 0

for (item in dataList) {
    processItem(item)
    processedCount++
    
    val currentTime = System.currentTimeMillis()
    if (processedCount % 10 == 0 || currentTime - lastUpdateTime > 500) {
        val progress = processedCount.toFloat() / dataList.size
        setProgress(workDataOf("progress" to progress))
        lastUpdateTime = currentTime
    }
}

17.4.2 进度数据的结构化

我习惯把进度数据封装成一个固定的结构,而不是随意拼字段。这样UI侧解析起来也统一:

object ProgressKeys {
    const val PROGRESS = "progress"      // 0.0 ~ 1.0
    const val STATUS = "status"          // 文本状态
    const val TOTAL = "total"            // 总任务量
    const val CURRENT = "current"        // 当前完成量
}

// Worker中
setProgress(workDataOf(
    ProgressKeys.PROGRESS to 0.75f,
    ProgressKeys.STATUS to "正在压缩图片...",
    ProgressKeys.TOTAL to 100,
    ProgressKeys.CURRENT to 75
))

17.4.3 处理任务取消时的进度

当用户取消任务时,WorkManager会调用onStopped()方法。这时候你应该把进度标记为「已取消」:

override fun onStopped() {
    super.onStopped()
    setProgress(workDataOf(
        "progress" to 0f,
        "status" to "任务已取消"
    ))
}

这样UI侧就能及时感知到任务被取消,并更新界面。

17.4.4 不要忘记清理观察者

这个其实是个老生常谈的问题,但我还是想提一下。如果你用LiveData观察进度,记得在View销毁时移除观察者。否则会导致内存泄漏。

用Flow的话,配合viewModelScope或者lifecycleScope,系统会自动管理生命周期。

总结一下我的经验:
  • setProgress() 是持久化的,App重启后依然有效
  • 控制更新频率,别让UI频繁刷新
  • 进度数据结构化,方便UI侧统一解析
  • 任务取消时也要更新进度状态
  • 记得清理观察者,避免内存泄漏

17.5 知识体系总览

下面这张图帮你梳理了本章的核心逻辑:

WorkManager 进度更新核心流程 Worker setProgress() WorkManager 数据库 WorkInfo UI 进度条 观察方式:LiveData / Flow 进度数据持久化,App重启后依然可恢复 ✅ 控制更新频率(每500ms或每10条数据) ✅ 进度数据结构化 ✅ 任务取消时更新状态

这张图展示了从Worker执行任务,到最终UI展示进度条的完整链路。核心就是:Worker调用setProgress() → 数据持久化到WorkManager数据库 → UI通过WorkInfo观察进度变化。

好了,关于进度更新的内容就讲到这里。记住一句话:让用户看到进度,就是让用户感到安心。下次你的App再做后台任务,别忘了加上进度条。


公众号:蓝海资料掘金营,微信deep3321