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
相关产品推荐
相关产品推荐

