Ray 的记录站

日常开发实践记录

0%

生成下一帧(frame): 驱动 Flutter Framework

Flutter Framework 中的绝大部分代码都是通过 Engine 的帧渲染驱动执行的.

事件一般有如下几类:

  • 手势
  • 平台消息(设备本身产生的数据, 比如设备传感器数据)
  • 设备消息(设备状态改变, 比如旋转, APP 进入后台/前台, 内存报警, 设备设置改变等)
  • Future 或 HTTP 响应

事件发生后, 通过引擎驱动 Framework 代码执行.

过程简介:(待完善或修正及名词正规化)

  1. 某些外部事件发生后, 会有一个 Schedule Frame 消息发送给引擎. (这个过程可以跟着源码里面的某个事件来看)
  2. 引擎准备好后, 它会向 Framework 请求 Begin Frame, Framework 接收后, 会启动和 Ticker 相关的任务(如果有, 比如动画). 这个任务由于是基于 Ticker 的, 所以可以再次向引擎发送 Schedule Frame 并开始后续流程, 从而完成整个动画过程.
  3. 引擎接着发送 Draw Frame(由于是在微任务队列中串行执行, Begin frame 和 Draw Frame 执行顺序是可以保证的)给 Framework, Framework 收到后, 检查是否需要更新 layoutsize (这里就可以看看 Flutter 的布局原理了, 官方文档有).
  4. 当上述任务完成后, 就可以开始 paint 阶段了, 这个阶段仍然是 Framework 负责.
  5. 拿到 paint 的结果后, 将所有的渲染中间数据打包为一个 Scene, 然后发送给引擎, 由引擎负责后续处理并传递给 GPU.
  6. 最后, Framework 会调用 PostFrame 回调, 包含执行相关的非渲染类型的后续任务.

ScheduleFrame 的调用

在源码中可以知道

动图中 Frame 阶段即 Framework 中的渲染处理管线执行阶段, 细节如下图所示:

实际上, 不通过引擎也可以实现新帧的生成, 但不推荐这样的做法.(详细可以自己查阅相关资料)

估计看完上面的内容, 会有一个疑问: 在 Draw Frame 中更新的 Layout 和 Size 是谁的?

这就要从 Flutter Framework 的内部入手了, 下面就来详细讲解.

从代码角度进行分析

首先在 runApp 时, 会进行:

1
2
3
4
5
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}

其中 WidgetsFlutterBinding.ensureInitialized() 即 binding 的初始化过程, 它的实现如下:

1
2
3
4
5
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}

通过 mixin 的顺序从后往前执行 initInstances 方法. 每个 mixin 中重写的 initInstances 都会被调用, 如果在其中调用 super 的话, 实际是调用前面一个 mixin 中的内容, 直到到达 WidgetsFlutterBinding 的父类 BindingBase.

这些 Binding 值得挨个看看. 针对帧渲染驱动流程, 监听引擎消息的是 SchedulerBinding. 在所有的 binding 都初始化之后(正常情况下只会进行一次), 会调用 scheduleWarmUpFrame 方法, 在其中调用 SchedulerBindingscheduleFrame 实现:

1
2
3
4
5
6
void scheduleFrame() {
// ...
ensureFrameCallbacksRegistered();
window.scheduleFrame();
// ...
}

其中 ensureFrameCallbacksRegistered 方法:

1
2
3
4
5
@protected
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}

这样就启动了监听引擎两个主要消息的流程.

_handleBeginFrame 中实际主要调用 handleBeginFrame 方法, 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void handleBeginFrame(Duration rawTimeStamp) {
// ...
try {
// ...

// 清空瞬时 callback 数组后, 调用瞬时 callback 数组中的所有回调(在初始化的时候没有)
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
// ...
} finally {
// ...
}
}

注册瞬时 callback: 通过 scheduleFrameCallback 进行注册, 在没有执行之前取消: cancelFrameCallbackWithId, 执行后会自动移除注册的瞬时回调.
(瞬时 callback 的相关处理细节.)

瞬时回调的处理

瞬时回调的一个主要作用: 注册 Ticker 下一次 tick 的 callback.

1
2
3
4
5
6
@protected
void scheduleTick({ bool rescheduling = false }) {
assert(!scheduled);
assert(shouldScheduleTick);
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}

另外也可以有一些自定义的用途(比如避免出现在 build 过程中改变状态).

handleDrawFrame

这个方法会在 handleBeginFrame 之后被调用(由引擎触发). 在其中主要调用 persistent callback(持久回调), 以及帧后回调(post frame callback). 这两种回调分别通过 addPersistentFrameCallbackaddPostFrameCallback 进行注册.

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp);

// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
_currentFrameTimeStamp = null;
}
}

在其中调用 _persistentCallbacks_postFrameCallbacks 中的回调, 另外持久回调数组调用后不会被清空.

初始化的时候, 有两个地方会注册持久回调:

  1. 注册 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.
    }
  2. WidgetInspectorService 中添加用于 GUI 检查的回调.

调用addPostFrameCallback 的地方就非常多了, 不一一展开. 帧后回调主要就是在本帧数据处理完成后进行额外处理.

至此, 引擎的 scheduFrame->beginFrame->drawFrame 流程总结完毕.