Flutter Web中如何执行耗时任务而不阻塞UI?
Flutter Web 处理耗时任务的替代方案
针对你遇到的compute在Flutter Web中失效、耗时任务阻塞UI的问题,以下是几种可行的替代方案:
一、使用 Dart Isolate(Web 兼容版)
Flutter Web 不支持compute,但可以直接用Isolate.spawn创建隔离线程执行耗时任务。注意Web环境的Isolate无法传递复杂对象,但Uint8List这类可序列化类型可以正常传递,且需要将耗时函数抽离为顶层/静态方法。
示例代码:
// 抽离顶层函数,供Isolate调用 Future<Uint8List> generatePDF(Uint8List imageBytes) async { final pdf = pw.Document(); pdf.addPage( pw.Page( pageFormat: PdfPageFormat.a4.landscape, build: (pw.Context context) { return pw.Image(pw.MemoryImage(imageBytes)); }, ), ); return await pdf.save(); } printChart() async { Future<Uint8List> captureWidgetToImage(GlobalKey key) async { RenderRepaintBoundary boundary = key.currentContext!.findRenderObject() as RenderRepaintBoundary; ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); return byteData!.buffer.asUint8List(); } Uint8List imageBytes = await captureWidgetToImage(_chartKey); // 创建接收端口,用于获取Isolate返回的结果 final receivePort = ReceivePort(); await Isolate.spawn( _generatePDFIsolate, _IsolateMessage(receivePort.sendPort, imageBytes), ); // 等待Isolate返回PDF数据 final pdfData = await receivePort.first as Uint8List; // 打开PDF逻辑不变 final blob = html.Blob([pdfData], 'application/pdf'); final url = html.Url.createObjectUrlFromBlob(blob); html.window.open(url, '_blank'); // 关闭接收端口 receivePort.close(); } // 用于传递Isolate消息的可序列化类 class _IsolateMessage { final SendPort sendPort; final Uint8List imageBytes; _IsolateMessage(this.sendPort, this.imageBytes); } // Isolate的入口函数 void _generatePDFIsolate(_IsolateMessage message) async { final pdfData = await generatePDF(message.imageBytes); message.sendPort.send(pdfData); Isolate.exit(); }
二、利用浏览器原生 Web Worker
如果Isolate的性能仍不够,可以直接使用浏览器的Web Worker,它是真正的独立线程,完全脱离主线程。缺点是无法直接调用Flutter Dart API,需要用JS或编译为JS的Dart代码实现PDF生成逻辑。
示例步骤:
- 创建独立的
pdf-worker.js文件:
self.onmessage = async function(e) { // 引入jsPDF库(需提前在项目中引入) const { imageBytes } = e.data; const pdf = new jsPDF('l', 'mm', 'a4'); // 将Uint8List转为base64图片 const imgData = 'data:image/png;base64,' + btoa(String.fromCharCode(...new Uint8Array(imageBytes))); pdf.addImage(imgData, 'PNG', 0, 0, 297, 210); // 返回PDF二进制数据 const pdfBytes = pdf.output('arraybuffer'); self.postMessage(pdfBytes); };
- 在Flutter Dart代码中调用Worker:
printChart() async { // 保留原有的captureWidgetToImage函数 Uint8List imageBytes = await captureWidgetToImage(_chartKey); // 创建Web Worker final worker = html.Worker('pdf-worker.js'); // 发送图片数据到Worker worker.postMessage(imageBytes); // 监听Worker返回的结果 worker.onMessage.listen((event) { final pdfData = Uint8List.fromList(event.data); // 打开PDF final blob = html.Blob([pdfData], 'application/pdf'); final url = html.Url.createObjectUrlFromBlob(blob); html.window.open(url, '_blank'); // 终止Worker释放资源 worker.terminate(); }); // 处理Worker错误 worker.onError.listen((error) { print('Worker执行出错: ${error.message}'); worker.terminate(); }); }
三、优化现有任务缩短耗时
除了移到后台线程,还可以从任务本身优化:
- 降低图片分辨率:将
pixelRatio从3.0调整为1.5或2.0,减少图片体积,大幅加快PDF生成速度。 - 拆分异步步骤:用
SchedulerBinding.instance.addPostFrameCallback在图片捕获后让UI刷新一次,再执行PDF生成,减少卡顿感知。 - 优化PDF库配置:检查
pdf库的参数,关闭不必要的压缩或冗余功能。
四、使用package:async的runAsync
如果任务耗时不算太长(2秒内),可以用package:async的runAsync函数,将任务放到事件循环的空闲队列执行,避免阻塞当前UI帧:
import 'package:async/async.dart'; printChart() async { // 保留原有的captureWidgetToImage和generatePDF函数 Uint8List imageBytes = await captureWidgetToImage(_chartKey); // 用runAsync执行耗时任务 final pdfData = await runAsync(() => generatePDF(imageBytes)); // 打开PDF逻辑不变 final blob = html.Blob([pdfData], 'application/pdf'); final url = html.Url.createObjectUrlFromBlob(blob); html.window.open(url, '_blank'); }
注意:这种方式没有开启新线程,只是调整任务执行时机,适合短耗时任务。
内容的提问来源于stack exchange,提问作者Priyantha Fernando
相关产品推荐
相关产品推荐

