Flutter中Workmanager任务崩溃中断、重复上传问题求助
解决方案:可靠的Flutter后台视频队列上传实现
针对你遇到的低内存设备后台任务被终止、重复上传、进度更新异常的问题,以下是针对性的解决思路和实现方案:
一、解决WorkManager任务被系统终止的问题
Android低内存环境下,普通后台进程优先级极低,容易被系统回收。WorkManager本身只是任务调度器,无法提升进程优先级,因此需要结合前台服务来保证任务存活:
- 在WorkManager的
callbackDispatcher中,启动一个前台服务(Android原生实现),显示带有上传进度的通知。系统会将前台服务进程的优先级提升,大幅降低被杀死的概率。 - 配置WorkManager任务约束:
Constraints constraints = Constraints( networkType: NetworkType.connected, // 仅在有网络时执行 requiresBatteryNotLow: true, // 低电量时暂停(可选) ); - 使用
enqueueUniqueWork而非一次性任务,避免重复调度:await Workmanager().enqueueUniqueWork( 'video_upload_queue', ExistingWorkPolicy.keep, // 已存在则保留,避免重复创建 WorkRequest.fromCallbackDispatcher( callbackDispatcher, constraints: constraints, ), );
二、彻底解决重复上传问题:任务幂等性+持久化状态
核心是通过本地数据库持久化每个任务的状态,确保任务不会重复执行:
- 给每个任务分配唯一ID:用视频文件的SHA-256哈希值(或文件路径+创建时间)作为唯一标识,避免因路径重复或任务重建导致的重复上传。
- 本地数据库存储任务状态:用
sqflite或Hive创建任务表,字段包括:task_id(唯一键)video_path(视频文件路径)status(待处理/处理中/已完成/上传失败)progress(0-100)
- 任务执行前校验状态:每次启动任务时,先查询数据库中该
task_id的状态:- 若为「已完成」:直接跳过
- 若为「处理中」:可选择继续执行或标记为失败重试
- 若为「待处理」:开始压缩上传流程
三、替换内存队列为持久化队列
你之前用StreamController维护的队列是内存级的,应用崩溃后会丢失,必须替换为本地持久化队列:
- 新增上传任务时,直接插入数据库,标记为「待处理」
- WorkManager任务启动后,从数据库中查询所有「待处理」的任务,按顺序执行
- 每完成一个任务,更新数据库中的状态为「已完成」,并触发UI更新
四、修复进度更新与Provider状态同步
Provider的状态依赖内存存储,应用重启后会丢失,需要和本地数据库联动:
- 进度实时写入数据库:在压缩、上传的回调中,每更新一次进度就同步写入数据库
- 应用启动时恢复状态:在
main函数中,先从数据库加载所有未完成的任务,初始化Provider的状态 - 用Stream监听数据库变化:使用
sqflite的查询流(或Hive的监听),让Provider自动感知数据库的进度更新,同步到UI层 - 避免在WorkManager回调中直接操作Provider:WorkManager的
callbackDispatcher运行在隔离线程,无法直接访问Provider,应通过写入数据库的方式间接同步状态
五、代码示例片段
1. 数据库任务实体(sqflite)
class UploadTask { final String taskId; final String videoPath; final String status; final int progress; UploadTask({ required this.taskId, required this.videoPath, required this.status, required this.progress, }); Map<String, dynamic> toMap() { return { 'task_id': taskId, 'video_path': videoPath, 'status': status, 'progress': progress, }; } static UploadTask fromMap(Map<String, dynamic> map) { return UploadTask( taskId: map['task_id'], videoPath: map['video_path'], status: map['status'], progress: map['progress'], ); } }
2. WorkManager任务调度与状态校验
void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { // 初始化数据库 final db = await DatabaseHelper.instance.database; // 查询待处理任务 List<Map<String, dynamic>> tasks = await db.query( 'upload_tasks', where: 'status = ?', whereArgs: ['pending'], orderBy: 'created_at ASC', ); for (var taskMap in tasks) { UploadTask uploadTask = UploadTask.fromMap(taskMap); // 校验任务状态,避免重复执行 var existingTask = await db.query( 'upload_tasks', where: 'task_id = ?', whereArgs: [uploadTask.taskId], ); if (existingTask.first['status'] == 'completed') continue; // 更新状态为处理中 await db.update( 'upload_tasks', {'status': 'processing'}, where: 'task_id = ?', whereArgs: [uploadTask.taskId], ); // 执行压缩+上传 try { // 压缩视频逻辑 String compressedPath = await compressVideo(uploadTask.videoPath); // 上传,实时更新进度 await uploadFile( compressedPath, onProgress: (progress) async { await db.update( 'upload_tasks', {'progress': progress}, where: 'task_id = ?', whereArgs: [uploadTask.taskId], ); }, ); // 标记为完成 await db.update( 'upload_tasks', {'status': 'completed', 'progress': 100}, where: 'task_id = ?', whereArgs: [uploadTask.taskId], ); } catch (e) { // 标记为失败,可设置重试次数 await db.update( 'upload_tasks', {'status': 'failed'}, where: 'task_id = ?', whereArgs: [uploadTask.taskId], ); } } return Future.value(true); }); }
3. Provider状态恢复
class UploadProvider extends ChangeNotifier { List<UploadTask> _tasks = []; final DatabaseHelper _dbHelper = DatabaseHelper.instance; UploadProvider() { _loadTasks(); _listenToTaskUpdates(); } Future<void> _loadTasks() async { _tasks = await _dbHelper.getAllTasks(); notifyListeners(); } void _listenToTaskUpdates() { _dbHelper.taskUpdates.listen((tasks) { _tasks = tasks; notifyListeners(); }); } }
内容的提问来源于stack exchange,提问作者Shreyansh Sharma
相关产品推荐
相关产品推荐

