You need to enable JavaScript to run this app. Flutter Canvas drawImage持续报Object has been disposed错误
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Canvas drawImage持续报Object has been disposed错误

自定义绘制流程中drawImage触发Object has been disposed的原因说明

异常触发的核心逻辑

Flutter渲染管线对每帧绘制的Canvas对象有严格的生命周期约束:

  • 当某一帧的绘制流程启动时,引擎会创建绑定到当前帧的原生Canvas实例,传入CustomPainter的paint方法中。
  • 只要paint方法的同步执行流程完全结束,引擎就会认为当前帧的所有绘制指令已经收集完成,立刻回收当前绑定的Canvas对象、以及关联的所有渲染上下文资源,这个回收过程是自动的,不会等待paint方法中声明的异步回调执行完成。

你代码中触发报错的drawImage逻辑,完全嵌套在三层异步回调链路里:

  1. rootBundle.load是异步文件IO操作
  2. ui.instantiateImageCodec是异步图片解码操作
  3. codec.getNextFrame是异步帧获取操作

整个异步链路走完的耗时,远高于单帧的绘制周期(单帧渲染通常在16ms内完成)。等所有异步回调真正执行、调用canvasWrapper.drawImage的时候,之前绑定的Canvas实例早就被引擎回收dispose了,此时操作已销毁的对象,自然会抛出Object has been disposed错误。

同实例下drawCircle可以正常运行的原因

drawCircle的调用是在_drawDetails方法的同步代码块中完成的:当执行到这行代码时,整个调用栈还处于paint方法的同步执行阶段,Canvas对象还未被引擎回收,绘制指令会立刻被写入当前帧的渲染队列,完全不等异步回调触发就已经完成了提交。
哪怕等后续异步回调执行时Canvas已经被销毁,drawCircle的同步调用早已执行完毕,自然不会触发异常。

正确的实现方式

drawImage的异常本质是把本应提前完成的异步资源准备工作,放到了paint的同步执行流里做,修正方向非常明确:

  • 绝对不要在paint方法内部发起异步图片加载、解码流程,也不要在paint方法里声明async逻辑(CustomPainter的paint方法本身是同步签名,声明async后返回的Future不会被引擎监听,只会让异步绘制时序完全脱离帧生命周期)
  • 把图片加载、解码流程提前到paint触发之前完成,将解码完成的可绘制ui.Image对象缓存为自定义Painter的成员变量,图片加载完成后主动触发一次重绘
  • 重绘触发后,在paint的同步执行流程中,直接使用缓存好的Image对象调用drawImage即可

参考正确结构:

class MyCustomPainter extends CustomPainter {
  // 提前缓存解码完成的图片对象
  ui.Image? cachedBkImage;

  MyCustomPainter({this.cachedBkImage});

  @override
  void paint(Canvas canvas, Size size) {
    // 所有绘制逻辑全部同步执行
    if (cachedBkImage != null) {
      canvas.drawImage(cachedBkImage!, const Offset(10, 20), Paint());
    }
    final paint1 = Paint()
      ..color = const Color(0xff63aa65)
      ..style = PaintingStyle.fill;
    canvas.drawCircle(const Offset(15, 25), 8.0, paint1);
  }

  @override
  bool shouldRepaint(covariant MyCustomPainter oldDelegate) {
    // 图片加载完成后缓存更新时触发重绘
    return oldDelegate.cachedBkImage != cachedBkImage;
  }
}

// 在业务组件中提前异步加载图片
Future<void> loadAssetImage() async {
  final bd = await rootBundle.load("assets/test1.png");
  final lst = Uint8List.view(bd.buffer);
  final codec = await ui.instantiateImageCodec(lst);
  final frameInfo = await codec.getNextFrame();
  // 把解码完成的图片传给Painter,触发重绘
  painter.cachedBkImage = frameInfo.image;
  setState(() {});
}

补充注意点:代码中出现的canvasWrapper.save()与canvasWrapper.restore()必须成对匹配,否则也会引发栈结构错乱的相关异常。


内容的提问来源于stack exchange,提问作者user8012596

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 11:06:07