Flutter中Workmanager后台上传进度无法更新UI的问题解决
Workmanager的callbackDispatcher运行在独立的隔离Isolate中,和主UI线程的内存完全隔离。你在主线程定义的顶级uploadPercent(ValueNotifier)和videoUploadingProvider,在后台任务里是完全独立的实例——更新后台实例的值,根本不会同步到UI线程的实例上,这就是直接调用正常、用Workmanager失效的核心原因。
下面提供两种可行的实现方式,根据你的需求选择:
方案一:用SharedPreferences存储进度(简单易实现)
通过SharedPreferences在后台任务和UI线程之间共享进度数据,UI侧定时读取并更新。
1. 后台任务修改进度存储逻辑
在uploadAsset的onChanged回调中,将计算后的进度写入SharedPreferences,用任务的唯一ID作为键,避免多任务冲突:
await uploadAsset( rid: uploadVideoData?.rId ?? 0, grpId: uploadVideoData?.groupId ?? 0, pId: uploadVideoData?.pId ?? 0, asset: File(compressedVideoPath), onChanged: (percent) async { double calculatedPercent = uploadVideoData?.fromGallery == true ? ((percent * 100) / 2) + 50 : percent * 100; // 写入SharedPreferences final prefs = await SharedPreferences.getInstance(); await prefs.setDouble('upload_progress_$uniqueId', calculatedPercent); debugPrint('Uploading percent is: $calculatedPercent'); }, );
注意:要把任务的uniqueId传递到uploadVideos和uploadAsset函数中。
2. UI侧监听进度更新
在UI页面中,通过定时读取SharedPreferences来更新进度UI:
class UploadProgressWidget extends StatefulWidget { final String taskUniqueId; const UploadProgressWidget({super.key, required this.taskUniqueId}); @override State<UploadProgressWidget> createState() => _UploadProgressWidgetState(); } class _UploadProgressWidgetState extends State<UploadProgressWidget> { late ValueNotifier<double> _progressNotifier; late SharedPreferences _prefs; Timer? _progressTimer; @override void initState() { super.initState(); _progressNotifier = ValueNotifier(0); _initPrefs(); // 每500ms读取一次进度,平衡实时性和性能 _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (_) => _updateProgress()); } Future<void> _initPrefs() async { _prefs = await SharedPreferences.getInstance(); await _updateProgress(); } Future<void> _updateProgress() async { final progress = _prefs.getDouble('upload_progress_${widget.taskUniqueId}') ?? 0; _progressNotifier.value = progress; // 进度完成后清理缓存并停止定时器 if (progress >= 100) { _progressTimer?.cancel(); await _prefs.remove('upload_progress_${widget.taskUniqueId}'); } } @override void dispose() { _progressTimer?.cancel(); _progressNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ValueListenableBuilder<double>( valueListenable: _progressNotifier, builder: (context, progress, _) { return LinearProgressIndicator(value: progress / 100); }, ); } }
方案二:Isolate通信(实时性更高)
利用Isolate的SendPort/ReceivePort机制,直接在后台任务和主UI线程之间传递进度消息,适合对实时性要求高的场景。
1. 主线程初始化通信端口
在main函数中创建ReceivePort,监听后台任务的进度消息:
// 主Isolate的通信端口 final ReceivePort _progressReceivePort = ReceivePort(); // 用Map管理多个任务的进度 final ValueNotifier<Map<String, double>> uploadProgressMap = ValueNotifier({}); void main() async { WidgetsFlutterBinding.ensureInitialized(); // 监听后台传来的进度消息 _progressReceivePort.listen((message) { if (message is Map<String, dynamic>) { final taskId = message['taskId'] as String; final progress = message['progress'] as double; // 更新进度Map uploadProgressMap.value = {...uploadProgressMap.value, taskId: progress}; } }); // 初始化Workmanager await Workmanager().initialize( callbackDispatcher, isInDebugMode: true, ); runApp(const YourApp()); }
2. 注册任务时传递通信端口
注册Workmanager任务时,把SendPort的序列化数据传入inputData:
String uniqueId = '${uploadVideoData.rId}_${uploadVideoData.pId}_${uploadVideoData.groupId}'; await Workmanager().registerOneOffTask( uniqueId, uniqueId, inputData: { ...uploadVideoData.toJson(), // 序列化SendPort 'sendPort': _progressReceivePort.sendPort.toRawSendPort().toJson(), 'taskId': uniqueId, }, );
3. 后台任务发送进度消息
在callbackDispatcher中还原SendPort,然后在进度更新时发送消息到主线程:
@pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { if (inputData != null) { // 还原SendPort final rawSendPort = RawSendPort.fromJson(inputData['sendPort']); final sendPort = SendPort.fromRawSendPort(rawSendPort); final taskId = inputData['taskId'] as String; await uploadVideos( uploadVideoData: UploadVideoData.fromJson(inputData), sendPort: sendPort, taskId: taskId, ); } return Future.value(true); }); } // 修改uploadVideos函数,接收通信参数 Future<void> uploadVideos({ required UploadVideoData uploadVideoData, required SendPort sendPort, required String taskId, }) async { // ...其他逻辑 await uploadAsset( rid: uploadVideoData?.rId ?? 0, grpId: uploadVideoData?.groupId ?? 0, pId: uploadVideoData?.pId ?? 0, asset: File(compressedVideoPath), onChanged: (percent) { double calculatedPercent = uploadVideoData?.fromGallery == true ? ((percent * 100) / 2) + 50 : percent * 100; // 发送进度到主UI线程 sendPort.send({ 'taskId': taskId, 'progress': calculatedPercent, }); debugPrint('Uploading percent is: $calculatedPercent'); }, ); // ...其他逻辑 }
4. UI侧监听进度Map
在UI中通过ValueListenableBuilder监听进度Map的变化:
ValueListenableBuilder<Map<String, double>>( valueListenable: uploadProgressMap, builder: (context, progressMap, _) { final progress = progressMap[yourTaskUniqueId] ?? 0; return LinearProgressIndicator(value: progress / 100); }, );
- 后台任务完成后,记得清理SharedPreferences中的进度缓存(方案一),或者发送完成消息通知主线程移除进度记录(方案二)。
- Android平台下,Workmanager任务可能在应用被杀后继续运行,UI重启时需要读取SharedPreferences恢复进度状态。
内容的提问来源于stack exchange,提问作者Shreyansh Sharma

