记得刚工作那会,有幸基于Flutter Engine做二次开发,彼时的我刚出校园,没有接触过如此复杂的框架,只能如同蚕虫一点一点啃食桑叶一样,慢慢地阅读源码。如今已转业务做其他工作,做下记录,纪念当时的艰难,以及后续如果重回Flutter怀抱时能快速拾起。
Flutter 分为Framework仓库和Engine仓库,Framework仓库基于Dart语言,封装了很多基本Widget,方便快速搭建优美的界面,Engine为Framework提供了丰富和高性能的绘图能力。Engine的高性能绘图能力一方面基于Skia库提供的绘图功能,另一方面也得益于Engine layer层的设计,避免了不必要的重绘。本文会通过阅读源码来分析整个绘图流程,为了方便理解,会通过一个简单demo作为输入进行分析
通过本文,你能了解到:
- Flutter Engine整体绘制流程
- Engine layer层的设计
- 如何减少不必要的重绘
下文源码来自Engine仓库,git版本:src/flutter: (HEAD detached at 992cdb6cd4)
layer简介
请注意,因为framework有很多与engine一一对应的对象,名称也一样。 如果没有额外说明,下文所指对象都为engine对象
在flutter框架的设计中,framework的所有界面绘制都会通过构造一个layer tree传递给engine,engine再通过操作layer tree调用Skia的相关api进行绘制显示,layer tree是layer的集合,layer是图层的意思,代表了绘图的基本操作单位。下面是layer的家族(不全,只列出下文会提及到的)
ContainerLayer
容器layer,此Layer允许有子layer,原因是它有成员变量std::vector<std::shared_ptr> layers_
TransformLayer
继承于ContainerLayer,但额外带了transform信息,即平移(x,y), 从它的构造函数可以看到需要提供SkMatrix对象
class TransformLayer : public ContainerLayer {
public:
explicit TransformLayer(const SkMatrix& transform);
}
DisplayListLayer
稍微比较复杂,它表示了一系列绘图操作集合。举个例子:
// main.dart
Picture drawRectToPicture(Offset offset,Size size){
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
// 画一个300 x 300 的矩形
canvas.drawRect( offset & size, Paint()..color = Colors.green);
// 再画一个300 x 300 的矩形
canvas.drawRect( offset & size, Paint()..color = Colors.green);
return recorder.endRecording();
}
drawRectToPicture函数返回了一个Dart对象Picture,这个Picture里面有两个三角形,阅读代码会发现Picture并不是对应一张图片,即它并没有生成bitmap,而是持有一个成员变量DisplayListLayer(engine对象),这个DisplayListLayer有一块内存保存了绘图信息,读取它会解析出如下绘制指令:
- op(rect) ,msg(x y width height)
- op(rect) ,msg(x y width height)
后续真正的渲染上屏操作会通过上述指令进行实际的绘图操作,当然,这些都是在engine里做的
Layer Demo
下面是用Dart编写的demo,通过直接操作SceneBuilder手动构造一个layer tree,并触发绘制。下面的demo生成的layer tree如下(有层级结构,低层级是上一个层级的子layer):
|---ContainerLayer(ps:根layer)
|------TransformLayer (ps:保存了平移(50,50)的操作,由sceneBuilder.pushOffset(50,50)添加)
|----------DispalyListLayer (ps:保存了矩形信息,sceneBuilder.addPicture(Offset.zero, picture)调用时添加)
// main.dart
void beginFrame(){
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
// canvas绘制
// 从300,300坐标开始绘制
Offset offset = Offset(300, 300);
// 绘制区域是300x300的区域
Size size = Size(300, 300);
canvas.drawRect(offset & size, Paint()..color = Colors.green);
// 通过recorder.endRecording结束节点绘制并返回一个Picture
Picture picture = recorder.endRecording();
// 三、初始化一个SceneBuilder
SceneBuilder sceneBuilder = SceneBuilder();
// 通过SceneBuilder上的方法将上诉canvas生成的Picture添加到engine
sceneBuilder.pushOffset(50, 50);
sceneBuilder.addPicture(Offset.zero, picture);
sceneBuilder.pop();
// 四、通过sceneBuilder.build生成scene
Scene scene = sceneBuilder.build();
window.render(scene);
}
void main() {
window.onDrawFrame = beginFrame;
// 触发一个VSync信号,在下一帧触发onDrawFrame回调
window.scheduleFrame();
}
代码解析:
- 生成一个Picture对象,此Picture会绘制一个(300,300,600,600) 的绿色矩形
- 初始化SceneBuilder 对象,初始化时会给初始化一个根layer,是ContainerLayer类型,后称root layer
- 调用sceneBuilder.pushOffset(50, 50),为root layer加入一个子layer:TransformLayer
- 调用sceneBuilder.addPicture(Offset.zero, picture),为TransformLayer加入一个子layer:DispalyListLayer
- 调用sceneBuilder.build() 生成一个Scene对象,此时本质上是将root layer作为参数构造一个Scene对象,Scene构造时创建一个LayerTree(看src/flutter/lib/ui/compositing/scene.cc)
// src/flutter/lib/ui/compositing/scene_builder.cc
void SceneBuilder::build(Dart_Handle scene_handle) {
FML_DCHECK(layer_stack_.size() >= 1);
// layer_stack_[0] 是 root layer
Scene::create(
scene_handle, std::move(layer_stack_[0]), rasterizer_tracing_threshold_,
checkerboard_raster_cache_images_, checkerboard_offscreen_layers_);