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

App重启后如何通过WorkManager更新视频上传进度UI?

解决方法

核心思路

问题本质是应用被内存清理后,ViewModel持有的Work任务标识丢失,新实例无法关联到正在运行的上传任务。解决关键是把任务标识或进度持久化到本地存储,无需依赖广播接收器即可恢复状态同步。


方案一:持久化Work任务ID,重启后重新监听

步骤1:启动任务时保存ID

修改uploadVideo方法,将生成的WorkRequest ID存入SharedPreferences:

private fun uploadVideo(chunk: VideoData) {
    if (!appContext.isOnline(analyticsManager)) {
        showNoNetwork.value = true
        return
    }

    val workManager = WorkManager.getInstance(appContext)
    val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadVideoWorkManager>()
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .setInputData(
            workDataOf(
                ParamKeys.VIDEO to videoData.value?.toJsonString()
            )
        )
        .build()
    workManager.enqueue(uploadWorkRequest)
    
    // 保存任务ID到本地
    val prefs = appContext.getSharedPreferences("upload_prefs", Context.MODE_PRIVATE)
    prefs.edit().putString("current_upload_work_id", uploadWorkRequest.id.toString()).apply()
    
    // 原有监听逻辑,增加任务完成后的ID清理
    val liveData = workManager.getWorkInfoByIdLiveData(uploadWorkRequest.id)
    workInfoObserver = Observer { workInfo ->
        updateUploadStatus(workInfo, liveData)
        // 任务结束后删除本地存储的ID
        if (workInfo.state.isFinished) {
            prefs.edit().remove("current_upload_work_id").apply()
        }
    }
    liveData.observeForever(workInfoObserver!!)
}

步骤2:ViewModel初始化时恢复监听

在ViewModel的初始化块中,检查本地是否有未完成的任务ID,若存在则重新绑定监听:

init {
    val prefs = appContext.getSharedPreferences("upload_prefs", Context.MODE_PRIVATE)
    val workIdStr = prefs.getString("current_upload_work_id", null)
    workIdStr?.let {
        val workId = UUID.fromString(it)
        val workManager = WorkManager.getInstance(appContext)
        val liveData = workManager.getWorkInfoByIdLiveData(workId)
        workInfoObserver = Observer { workInfo ->
            updateUploadStatus(workInfo, liveData)
            if (workInfo.state.isFinished) {
                prefs.edit().remove("current_upload_work_id").apply()
            }
        }
        liveData.observeForever(workInfoObserver!!)
    }
}

注意事项

在ViewModel销毁时移除永久观察者,避免内存泄漏:

override fun onCleared() {
    super.onCleared()
    workInfoObserver?.let { observer ->
        val prefs = appContext.getSharedPreferences("upload_prefs", Context.MODE_PRIVATE)
        prefs.getString("current_upload_work_id", null)?.let {
            val workId = UUID.fromString(it)
            WorkManager.getInstance(appContext).getWorkInfoByIdLiveData(workId)?.removeObserver(observer)
        }
    }
}

方案二:使用UniqueWorkRequest(单任务场景)

如果应用同一时间仅支持单个视频上传,可使用UniqueWorkRequest指定唯一任务名称,无需存储ID,直接通过名称关联任务:

启动任务时使用UniqueWork

private fun uploadVideo(chunk: VideoData) {
    if (!appContext.isOnline(analyticsManager)) {
        showNoNetwork.value = true
        return
    }

    val workManager = WorkManager.getInstance(appContext)
    val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadVideoWorkManager>()
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .setInputData(
            workDataOf(
                ParamKeys.VIDEO to videoData.value?.toJsonString()
            )
        )
        .build()
    
    // 以唯一名称入队,可选择替换/保留现有同名任务
    workManager.enqueueUniqueWork(
        "single_video_upload",
        ExistingWorkPolicy.REPLACE,
        uploadWorkRequest
    )
    
    // 监听同名任务的状态
    val liveData = workManager.getWorkInfosForUniqueWorkLiveData("single_video_upload")
    workInfoObserver = Observer { workInfos ->
        workInfos.firstOrNull()?.let { workInfo ->
            updateUploadStatus(workInfo, liveData)
        }
    }
    liveData.observeForever(workInfoObserver!!)
}

重启后恢复监听

ViewModel初始化时直接通过唯一名称绑定监听:

init {
    val workManager = WorkManager.getInstance(appContext)
    val liveData = workManager.getWorkInfosForUniqueWorkLiveData("single_video_upload")
    workInfoObserver = Observer { workInfos ->
        workInfos.firstOrNull()?.let { workInfo ->
            if (!workInfo.state.isFinished) {
                updateUploadStatus(workInfo, liveData)
            }
        }
    }
    liveData.observeForever(workInfoObserver!!)
}

方案三:持久化上传进度到本地数据库(精准进度展示)

若需要展示更细致的上传进度(如已上传块数),可在Worker中将进度同步到Room数据库,ViewModel监听数据库LiveData实现状态同步:

1. 定义进度实体与Dao

@Entity(tableName = "upload_progress")
data class UploadProgress(
    @PrimaryKey val videoId: String,
    val uploadedChunks: Int,
    val totalChunks: Int,
    val workId: String,
    val state: WorkInfo.State
)

@Dao
interface UploadProgressDao {
    @Query("SELECT * FROM upload_progress WHERE videoId = :videoId")
    fun getProgress(videoId: String): LiveData<UploadProgress?>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun updateProgress(progress: UploadProgress)
    
    @Delete
    suspend fun deleteProgress(progress: UploadProgress)
}

2. Worker中更新进度

在UploadVideoWorkManager的doWork方法中,每完成一块上传就同步进度到数据库:

class UploadVideoWorkManager(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    private val dao = AppDatabase.getInstance(context).uploadProgressDao()
    
    override suspend fun doWork(): Result {
        val videoJson = inputData.getString(ParamKeys.VIDEO) ?: return Result.failure()
        val videoData = videoJson.parseToVideoData() // 假设你有解析方法
        val totalChunks = videoData.totalChunks

        repeat(totalChunks) { index ->
            // 执行块上传逻辑
            uploadSingleChunk(videoData, index)
            
            // 更新进度到数据库
            dao.updateProgress(
                UploadProgress(
                    videoId = videoData.id,
                    uploadedChunks = index + 1,
                    totalChunks = totalChunks,
                    workId = id.toString(),
                    state = WorkInfo.State.RUNNING
                )
            )
            
            // 任务被取消时终止上传
            if (isStopped) return Result.success()
        }

        // 上传完成后更新状态
        dao.updateProgress(
            UploadProgress(
                videoId = videoData.id,
                uploadedChunks = totalChunks,
                totalChunks = totalChunks,
                workId = id.toString(),
                state = WorkInfo.State.SUCCEEDED
            )
        )
        return Result.success()
    }
}

3. ViewModel监听数据库进度

ViewModel直接监听Room的LiveData,同时结合WorkManager状态处理任务结束逻辑:

class UploadViewModel(...) : ViewModel() {
    private val dao = AppDatabase.getInstance(appContext).uploadProgressDao()
    // 假设currentVideoId是当前上传视频的ID
    val uploadProgress = dao.getProgress(currentVideoId)

    init {
        uploadProgress.observeForever { progress ->
            progress?.let {
                val workId = UUID.fromString(it.workId)
                val workManager = WorkManager.getInstance(appContext)
                workManager.getWorkInfoByIdLiveData(workId).observeForever { workInfo ->
                    if (workInfo.state.isFinished) {
                        viewModelScope.launch {
                            dao.deleteProgress(it)
                        }
                    }
                }
            }
        }
    }
}

以上方案均无需使用广播接收器,核心是通过本地持久化让新ViewModel实例能关联到之前的Work任务。可根据业务场景(单/多任务、进度精度需求)选择合适的方案。

内容的提问来源于stack exchange,提问作者andro-girl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 14:52:52