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

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,
      ),
    );
    

二、彻底解决重复上传问题:任务幂等性+持久化状态

核心是通过本地数据库持久化每个任务的状态,确保任务不会重复执行:

  1. 给每个任务分配唯一ID:用视频文件的SHA-256哈希值(或文件路径+创建时间)作为唯一标识,避免因路径重复或任务重建导致的重复上传。
  2. 本地数据库存储任务状态:用sqflite或Hive创建任务表,字段包括:
    • task_id(唯一键)
    • video_path(视频文件路径)
    • status(待处理/处理中/已完成/上传失败)
    • progress(0-100)
  3. 任务执行前校验状态:每次启动任务时,先查询数据库中该task_id的状态:
    • 若为「已完成」:直接跳过
    • 若为「处理中」:可选择继续执行或标记为失败重试
    • 若为「待处理」:开始压缩上传流程

三、替换内存队列为持久化队列

你之前用StreamController维护的队列是内存级的,应用崩溃后会丢失,必须替换为本地持久化队列:

  • 新增上传任务时,直接插入数据库,标记为「待处理」
  • WorkManager任务启动后,从数据库中查询所有「待处理」的任务,按顺序执行
  • 每完成一个任务,更新数据库中的状态为「已完成」,并触发UI更新

四、修复进度更新与Provider状态同步

Provider的状态依赖内存存储,应用重启后会丢失,需要和本地数据库联动:

  1. 进度实时写入数据库:在压缩、上传的回调中,每更新一次进度就同步写入数据库
  2. 应用启动时恢复状态:在main函数中,先从数据库加载所有未完成的任务,初始化Provider的状态
  3. 用Stream监听数据库变化:使用sqflite的查询流(或Hive的监听),让Provider自动感知数据库的进度更新,同步到UI层
  4. 避免在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:08:23