生成下一帧(frame): 驱动 Flutter Framework
本文最后更新于 2021年5月17日 晚上
Flutter Framework 中的绝大部分代码都是通过 Engine 的帧渲染驱动执行的.
事件一般有如下几类:
- 手势
- 平台消息(设备本身产生的数据, 比如设备传感器数据)
- 设备消息(设备状态改变, 比如旋转, APP 进入后台/前台, 内存报警, 设备设置改变等)
- Future 或 HTTP 响应
事件发生后, 通过引擎驱动 Framework 代码执行.
过程简介:(待完善或修正及名词正规化)
- 某些外部事件发生后, 会有一个
Schedule Frame
消息发送给引擎. (这个过程可以跟着源码里面的某个事件来看) - 引擎准备好后, 它会向 Framework 请求
Begin Frame
, Framework 接收后, 会启动和 Ticker 相关的任务(如果有, 比如动画). 这个任务由于是基于 Ticker 的, 所以可以再次向引擎发送 Schedule Frame 并开始后续流程, 从而完成整个动画过程. - 引擎接着发送
Draw Frame
(由于是在微任务队列中串行执行, Begin frame 和 Draw Frame 执行顺序是可以保证的)给 Framework, Framework 收到后, 检查是否需要更新layout
和size
(这里就可以看看 Flutter 的布局原理了, 官方文档有). - 当上述任务完成后, 就可以开始
paint
阶段了, 这个阶段仍然是 Framework 负责. - 拿到 paint 的结果后, 将所有的渲染中间数据打包为一个
Scene
, 然后发送给引擎, 由引擎负责后续处理并传递给 GPU. - 最后, Framework 会调用 PostFrame 回调, 包含执行相关的非渲染类型的后续任务.
ScheduleFrame 的调用
在源码中可以知道
动图中 Frame 阶段即 Framework 中的渲染处理管线执行阶段, 细节如下图所示:
实际上, 不通过引擎也可以实现新帧的生成, 但不推荐这样的做法.(详细可以自己查阅相关资料)
估计看完上面的内容, 会有一个疑问: 在 Draw Frame
中更新的 Layout 和 Size 是谁的?
这就要从 Flutter Framework 的内部入手了, 下面就来详细讲解.
从代码角度进行分析
首先在 runApp 时, 会进行:
1 |
|
其中 WidgetsFlutterBinding.ensureInitialized()
即 binding 的初始化过程, 它的实现如下:
1 |
|
通过 mixin 的顺序从后往前执行 initInstances
方法. 每个 mixin 中重写的 initInstances
都会被调用, 如果在其中调用 super
的话, 实际是调用前面一个 mixin 中的内容, 直到到达 WidgetsFlutterBinding
的父类 BindingBase
.
这些 Binding 值得挨个看看. 针对帧渲染驱动流程, 监听引擎消息的是 SchedulerBinding
. 在所有的 binding 都初始化之后(正常情况下只会进行一次), 会调用 scheduleWarmUpFrame
方法, 在其中调用 SchedulerBinding
的 scheduleFrame
实现:
1 |
|
其中 ensureFrameCallbacksRegistered
方法:
1 |
|
这样就启动了监听引擎两个主要消息的流程.
在 _handleBeginFrame
中实际主要调用 handleBeginFrame
方法, 实现如下:
1 |
|
注册瞬时 callback: 通过 scheduleFrameCallback
进行注册, 在没有执行之前取消: cancelFrameCallbackWithId
, 执行后会自动移除注册的瞬时回调.
(瞬时 callback 的相关处理细节.)
瞬时回调的处理
瞬时回调的一个主要作用: 注册 Ticker 下一次 tick 的 callback.
1 |
|
另外也可以有一些自定义的用途(比如避免出现在 build 过程中改变状态).
handleDrawFrame
这个方法会在 handleBeginFrame
之后被调用(由引擎触发). 在其中主要调用 persistent callback(持久回调), 以及帧后回调(post frame callback). 这两种回调分别通过 addPersistentFrameCallback
和 addPostFrameCallback
进行注册.
实现如下:
1 |
|
在其中调用 _persistentCallbacks
和 _postFrameCallbacks
中的回调, 另外持久回调数组调用后不会被清空.
初始化的时候, 有两个地方会注册持久回调:
注册
RendererBinding
中_handlePersistentFrameCallback
: 这个回调中执行的就是 Framework 端渲染管线.1
2
3
4
5
6
7
8
9@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}WidgetInspectorService
中添加用于 GUI 检查的回调.
调用addPostFrameCallback
的地方就非常多了, 不一一展开. 帧后回调主要就是在本帧数据处理完成后进行额外处理.
至此, 引擎的 scheduFrame->beginFrame->drawFrame 流程总结完毕.