Flutter中如何修改ui.Image或高性能绘制高频变动图像
Flutter高频小幅像素更新高性能渲染方案
核心思路:规避全量像素拷贝、绘制指令累积两类开销,仅同步增量修改的像素,复用常驻内存资源,把单帧计算复杂度控制在和修改像素数线性相关的水平,完全满足1000x1000分辨率60fps的更新要求。
最优原生实现:基于Picture快照的增量绘制
这个方案不需要写任何原生代码,纯Dart即可实现,性能足够覆盖当前场景:
- 初始化阶段
- 提前创建一个长度为
1000*1000的常驻Uint32List作为像素缓冲,仅初始化一次填充初始像素值,全程不做整体销毁重建 - 提前初始化复用的
Paint对象,避免每帧重复创建触发GC - 首次启动时录制初始全图的
ui.Picture作为第一帧的底版快照
- 提前创建一个长度为
- 每帧更新逻辑
- 绑定
WidgetsBinding.instance.addPostFrameCallback和屏幕刷新节奏对齐,保证60fps的更新节拍 - 创建新的
ui.PictureRecorder和Canvas,首先把上一帧留存的底版Picture直接画到Canvas上,这一步是GPU侧的纹理拷贝,O(1)开销,和图尺寸无关 - 仅针对本次修改的数十个像素,调用
canvas.drawPoints(PointMode.points, 更新点坐标列表, 复用的Paint对象)批量绘制,不要构造Path、不要循环调用单点位绘制接口 - 结束录制生成新的
ui.Picture,替换掉旧的底版快照,标记旧资源可回收
- 绑定
- 渲染层
用CustomPaint承载绘制,自定义CustomPainter的shouldRepaint方法固定返回true即可,每帧直接把最新的Picture画到画布上。
这个方案完全解决了之前用Path遇到的问题:每帧的绘制指令永远只有「画底版+画数十个点」,指令量完全固定,不会随运行时长增长。
极致性能可选:外接纹理方案
如果后续单帧修改像素量上涨、或者需要更低的渲染延迟,可以走Flutter的Texture外接纹理链路:
- Dart侧维护常驻的1000x1000像素
Uint8List缓冲,所有像素修改直接操作对应数组索引的内存值,不生成新的内存块 - 平台侧(Android用
SurfaceTexture、iOS用CVPixelBuffer)创建和缓冲内存绑定的纹理对象,直接注册给Flutter引擎 - 每帧完成像素修改后,仅需要通知引擎刷新纹理即可,全程没有任何跨内存拷贝、绘制指令录制的开销,性能和原生应用逐像素渲染完全一致。
原有方案性能问题根因
Image.memory方案:哪怕只改1个像素,每帧都要走100w像素的编码、解码、GPU上传全流程,单帧复杂度O(100w),内存和计算开销必然超标- 直接修改
ui.Image:ui.Image是GPU侧不可变纹理的封装,本身就不开放CPU侧像素修改接口,强行读回修改再上传的开销和Image.memory没有区别 - 纯Path方案:没有做帧快照,持续往同一个Path里追加绘制指令,指令量随运行时长线性增长,最终遍历和渲染开销会涨到不可接受的程度。
优化注意事项
- 像素坐标计算、缓冲值修改的逻辑放到独立Isolate执行,不要占用UI线程时间片,计算完成后再把需要更新的点集合发回UI线程
- 批量传点给
drawPoints接口,不要逐个点调用绘制方法,减少引擎层的调度开销 - 不要在帧回调里创建临时对象,所有绘制用到的配置类对象全部提前初始化复用。
内容的提问来源于stack exchange,提问作者ch271828n
相关产品推荐
相关产品推荐

