You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现从当前页返回上一页时展示图片下载进度条

实现方案

核心思路

要实现跨页面展示下载进度,核心是让下载任务的生命周期脱离当前页面的生命周期,同时用可全局观察的数据流托管下载进度,所有需要展示进度的页面直接订阅该数据流即可。


具体实现步骤

1. 定义下载状态结构体

统一封装进度、成功、失败等状态,方便上下游传递:

sealed class DownloadState {
    // 下载中:已完成数量、总数量
    data class InProgress(val completedCount: Int, val totalCount: Int): DownloadState()
    // 全部下载成功
    object Success: DownloadState()
    // 下载失败:对应错误提示资源ID
    data class Failed(val errorRes: Int): DownloadState()
    // 空闲状态
    object Idle: DownloadState()
}
2. 实现全局单例下载管理器

用生命周期和Application一致的单例托管下载任务,内部用StateFlow暴露可观察的下载状态:

// 如果用依赖注入框架(如Hilt)直接标注@Singleton即可
object ImageDownloadManager {
    private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.Idle)
    // 对外暴露只读的状态流,所有页面都可以订阅这个流拿进度
    val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()

    fun startDownloadTask(baseUrl: String, number: String, dispatcherProvider: DispatcherProvider, dataHandler: DataHandler, viewModel: YourViewModel) {
        // 避免重复启动下载
        if (_downloadState.value is DownloadState.InProgress) return
        val totalImageCount = 18 // 0~340步长20总共18张,可根据实际需求动态计算
        var completedCount = 0
        // 初始化为进度0状态
        _downloadState.value = DownloadState.InProgress(0, totalImageCount)

        // 用自定义的全局协程Scope替代GlobalScope,绑Application生命周期
        GlobalScope.launch(dispatcherProvider.io()) {
            for (angle in 0..340 step 20) {
                val formattedAngle = "%03d".format(angle)
                dataHandler.MyRequestHandler.getImage(
                    baseUrl.replace("340", formattedAngle),
                    formattedAngle
                ) { response ->
                    when {
                        response.isSuccessful && response.body() != null && response.networkResponse()?.code() == 200 -> {
                            try {
                                // 原有的图片存储逻辑保持不变
                                val imageByteArray = response.body()?.source()?.readByteArray()
                                val imageEntity = viewModel.imageEntityConverter(
                                    ImageModels("xyz", number, formattedAngle, "ownImage", imageByteArray)
                                )
                                viewModel.insertNumber(imageEntity)

                                // 原子更新进度
                                completedCount++
                                _downloadState.value = DownloadState.InProgress(completedCount, totalImageCount)
                                // 全部下载完成更新状态
                                if (completedCount == totalImageCount) {
                                    _downloadState.value = DownloadState.Success
                                }
                            } catch (e: Exception) {
                                Timber.e(e, "图片下载失败")
                                _downloadState.value = DownloadState.Failed(R.string.general_request_error)
                            }
                        }
                        response.networkResponse()?.code() == 404 -> {
                            _downloadState.value = DownloadState.Failed(R.string.error_fin_not_found_message)
                        }
                        else -> {
                            _downloadState.value = DownloadState.Failed(R.string.general_request_error)
                        }
                    }
                }
            }
        }
    }

    // 可选:提供取消下载的入口
    fun cancelDownload() {
        _downloadState.value = DownloadState.Idle
        // 可补充取消所有未完成网络请求的逻辑
    }
}
3. 修改当前下载页的逻辑

下载触发后直接返回上一页,不需要在下载回调里处理页面跳转,下载任务会在后台持续运行:

private fun downloadImage() {
    // 触发全局下载任务
    ImageDownloadManager.startDownloadTask(url, number, dispatcherProvider, dataHandler, viewModel)
    // 直接返回上一页即可,不需要等待下载回调
    activity.popSubPage()
}
4. 上一页订阅进度更新UI

在上一页的ViewModel中订阅下载状态流,UI层根据状态更新顶部进度条和RecyclerView对应条目:

  • ViewModel层逻辑:
class PreviousPageViewModel: ViewModel() {
    val downloadState = ImageDownloadManager.downloadState
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), DownloadState.Idle)
}
  • UI层逻辑(Activity/Fragment):
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.downloadState.collect { state ->
            when(state) {
                is DownloadState.InProgress -> {
                    // 更新顶部进度条
                    topProgressBar.visibility = View.VISIBLE
                    val progress = (state.completedCount * 100 / state.totalCount).toInt()
                    topProgressBar.progress = progress
                    // 更新RecyclerView对应下载条目的进度
                    val targetItem = yourList[downloadItemPosition]
                    targetItem.progress = progress
                    yourAdapter.notifyItemChanged(downloadItemPosition)
                }
                DownloadState.Success -> {
                    // 隐藏进度条,更新状态为已完成
                    topProgressBar.visibility = View.GONE
                    val targetItem = yourList[downloadItemPosition]
                    targetItem.progress = 100
                    targetItem.isDownloaded = true
                    yourAdapter.notifyItemChanged(downloadItemPosition)
                    Toast.makeText(context, "全部图片下载完成", Toast.LENGTH_SHORT).show()
                }
                is DownloadState.Failed -> {
                    topProgressBar.visibility = View.GONE
                    showErrorMessageDialog(R.string.failed, state.errorRes)
                }
                DownloadState.Idle -> {
                    topProgressBar.visibility = View.GONE
                }
            }
        }
    }
}

注意事项

  • 如果需要同时支持多个并行下载任务,可给每个下载任务加唯一标识,StateFlow改为存储Map<任务ID, DownloadState>,订阅时过滤对应ID的状态即可
  • 建议将回调式的网络请求转为协程挂起函数,方便管理协程取消,避免不必要的资源浪费
  • 可根据需求添加下载进度的本地缓存,进程被杀死后重启也能恢复未完成的下载

内容的提问来源于stack exchange,提问作者Manish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 19:36:03