Flutter 源码梳理系列(三十二):PictureRecorder、Picture

424 阅读6分钟

 在前面学习时,我们从来没有详细讲解过 PictureRecorder 和 Picture 的内容。其实主要是鉴于 PictureRecorder 和 Picture 是 Flutter framework 层到 Engine 层的桥接,它们真正的功能实现是在 Engine 层,在 Framework 层只能简单浏览一下它们的 API 看它们实现了什么功能而已。

 与 Canvas 类似,PictureRecorder、Picture 的实现类分别是:_NativePictureRecorder_NativePicture

PictureRecorder

 PictureRecorder:记录包含一系列图形操作(graphical operations)的图片(Picture)。

 要开始记录,构建一个 Canvas 来记录命令(即以一个 PictureRecorder 对象为参数构建一个 Canvas 对象)。要结束记录,则使用 PictureRecorder.endRecording 方法。

 如 PaintingContext 类中的 _startRecording 函数内部一样,当构建 Canvas 对象时,需要传入 ui.PictureRecorder? _recorder

  void _startRecording() {
  
    // ...
    _recorder = RendererBinding.instance.createPictureRecorder();
    _canvas = RendererBinding.instance.createCanvas(_recorder!);
    // ...
  }

 OK,下面看一下 PictureRecorder 的源码。

Constructors

 创建一个新的空闲 PictureRecorder。要将其与 Canvas 对象关联并开始记录,将此 PictureRecorder 传递给 Canvas 构造函数即可。

 注意 PictureRecorder 是一个抽象类,是无法直接创建实例对象使用的,但是看这里:PictureRecorder() 已经被声明为一个工厂函数,它会直接返回一个 _NativePictureRecorder 对象,_NativePictureRecorder 是一个实现了 PictureRecorder 的子类,所以这里其实是直接返回一个 _NativePictureRecorder 实例对象,而并不是说拿着 PictureRecorder 抽象类来构建实例对象使用。

abstract class PictureRecorder {
  factory PictureRecorder() = _NativePictureRecorder;
  
  // ...

isRecording

 表示这个 PictureRecorder 对象当前是否正在记录命令(Canvas 的绘制操作(graphical operations)或者 绘图命令)。

 具体来说,如果 Canvas 对象已经被创建用于记录命令且尚未通过调用 endRecording 方法结束记录,则返回 true;如果这个 PictureRecorder 尚未与 Canvas 相关联,或者 endRecording 方法已经被调用,则返回 false。

  bool get isRecording;

endRecording

 完成记录图形操作(graphical operations),即结束图形操作(graphical operations)记录。

 返回一个包含到目前为止已记录的图形操作(graphical operations)的图片(一个 Picture 对象)。调用此函数后,PictureRecorder 对象和 Canvas 对象都会失效,无法继续使用。

  Picture endRecording();

_NativePictureRecorder.endRecording

 在 _NativePictureRecorder 类中 endRecording 函数有一部分我们可以看懂的实现,如下:

  @override
  Picture endRecording() {
    // _NativePictureRecorder 有一个 _NativeCanvas? _canvas,即它会持有使用自己为参数创建的 Canvas,
    // 并且新创建的 Canvas 也会持有这个 _NativePictureRecorder,它俩会双向持有。
    
    // 如果 _canvas 属性为 null,则直接抛错,毕竟一个 _NativePictureRecorder 对象连 Canvas 都没有,
    // 就想生成 Picture,有点想多了,直接抛错即可。
    // 这也提示我们没有和 Canvas 绑定时是不能调用 _NativePictureRecorder 对象的 endRecording 函数的。
    if (_canvas == null) {
      throw StateError('PictureRecorder did not start recording.');
    }
    
    // 然后接下来为生成 Picture 对象做准备。
    
    // 准备一个 _NativePicture 对象,然后调用在 Engine 层实现的 _endRecording 函数,
    // 处理 _NativePicture 对象的事宜。
    final _NativePicture picture = _NativePicture._();
    _endRecording(picture);
    
    // 生成 _NativePicture 对象以后就无事了,
    // 就断开 _NativeCanvas 和 _NativePictureRecorder 的相互引用,
    // 没有了 _canvas 那么 _recorder 也要作废了。
    _canvas!._recorder = null;
    _canvas = null;
    
    // 我们在这里调用处理程序,而不是在 Picture 构造函数中调用,因为我们希望通过处理程序可以使用 picture.approximateBytesUsed。
    
    // 这里会回调一个 Picture 类的静态回调 onCreate。
    // 还有一个 Picture 类的静态回调 onDispose 函数,会在 Picture 类的 dispose 函数中,
    // 进行回调:Picture.onDispose?.call(this);
    Picture.onCreate?.call(picture);
    
    return picture;
  }

PictureRecorder 总结

 PictureRecorder 的 API 内容很少,到这里就结束了,我们只要记住两个点就可以了:

  1. 搭配 Canvas 使用,当创建 Canvas 对象时需要传入一个 PictureRecorder 对象。
  2. 它的 endRecording 方法会把记录到的所有图形操作生成一个 Picture 对象,并且在此函数调用后:Canvas 和 PictureRecorder 对象就都失效了。

 OK,下面我们看一下 Picture 的内容。

Picture

 Picture:一个表示一系列记录的绘图操作的对象。

 要创建一个 Picture,可以使用 PictureRecorder。

 可以使用 SceneBuilder 将 Picture 放置在一个 Scene 中,通过 SceneBuilder.addPicture 方法。也可以使用 Canvas.drawPicture 方法将 Picture 绘制到 Canvas 中。

 OK,下面看一下 Picture 的源码。

onCreate

 ⚠️ 注意这是一个 Picture 类的静态属性。

 一个被调用来报告 Picture 创建的回调函数。(在 Flutter 的 flutter/foundation.dart 中更倾向于使用 MemoryAllocations,而不是直接使用 onCreate,因为 MemoryAllocations 允许多个回调函数。)

 此回调可以在 _NativePictureRecorder 的 endRecording 函数中看到被调用了:Picture.onCreate?.call(picture);

// Picture 生命周期事件签名,入参为 Picture 返回值是 void
typedef PictureEventCallback = void Function(Picture picture);
  static PictureEventCallback? onCreate;

onDispose

 ⚠️ 注意这是一个 Picture 类的静态属性。

 一个被调用来报告 Picture 释放的回调函数。

  static PictureEventCallback? onDispose;

toImage

 异步的从这个 Picture 对象创建一张图像(Image)。

 返回的 Image 将具有 width 个像素宽和 height 个像素高。该 Image 在 0(left),0(top),width(right),height(bottom)边界内被栅格化。超出这些边界的内容将被裁剪。

 注意返回值是 Future,这是一个异步生成 Image 对象。下面还有一个同步的生成 Image 对象。

  Future<Image> toImage(int width, int height);

toImageSync

 同步地从这个 Picture 对象创建一张图像(Image)。

 返回的 Image 将具有 width 个像素宽和 height 个像素高。该 Image 在 0(left),0(top),width(right),height(bottom)边界内被栅格化。超出这些边界的内容将被裁剪。

 Image 对象是同步创建和返回的,但是是异步栅格化的。如果栅格化失败,当 Image 绘制到 Canvas 上时会抛出异常。

 如果有 GPU 上下文可用,此 Image 将被创建为 GPU 驻留,并且不会被复制回 host。这意味着绘制此 Image 将更高效。

 如果没有 GPU 上下文可用,该 Image 将在 CPU 上栅格化。

  Image toImageSync(int width, int height);

dispose

 调用此方法释放该 Picture 对象使用的资源。在调用此方法后,该 Picture 对象将不再可用。

  void dispose();

approximateBytesUsed

 返回为此 Picture 对象分配的大约字节数。实际 Picture 的大小可能会更大,特别是如果它包含对 image 或其他大对象的引用。

  int get approximateBytesUsed;

Picture 总结

 除了两个 Picture 类的静态回调:onCreate 和 onDispose 外,还有一对异步/同步生成 Image 的接口,关于此一对接口可以先通过大佬的文章学习:Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

参考链接

参考链接:🔗