一次绘制过程,称其为一帧(frame)
Flutter 可以实现60fps(Frame Per-Second)就是指一秒钟最多可以触发 60 次重绘,FPS 值越大,界面就越流畅
Flutter中 的 frame 概念并不等同于屏幕刷新帧(frame),因为Flutter UI 框架的 frame 并不是每次屏幕刷新都会触发,这是因为,如果 UI 在一段时间不变,那么每次屏幕刷新都重新走一遍渲染流程是不必要的,因此,Flutter 在第一帧渲染结束后会采取一种主动请求 frame 的方式来实现只有当UI可能会改变时才会重新走渲染流程。
- Flutter 在
window上注册一个onBeginFrame和一个onDrawFrame回调,在onDrawFrame回调中最终会调用drawFrame。 - 当我们调用
window.scheduleFrame()方法之后,Flutter引擎会在合适的时机(可以认为是在屏幕下一次刷新之前,具体取决于Flutter引擎的实现)来调用onBeginFrame和onDrawFrame。
可以看见,只有主动调用scheduleFrame(),才会执行 drawFrame。所以,在Flutter 中的提到 frame 时,如无特别说明,则是和 drawFrame() 的调用对应,而不是和屏幕的刷新频率对应。
Flutter 调度过程 SchedulerPhase
Flutter 应用执行过程简单来讲分为 idle 和 frame 两种状态,idle 状态代表没有 frame 处理,如果应用状态改变需要刷新 UI,则需要通过scheduleFrame()去请求新的 frame,当 frame 到来时,就进入了frame状态,整个Flutter应用生命周期就是在 idle 和 frame 两种状态间切换
1、frame 处理流程
当有新的 frame 到来时,具体处理过程就是依次执行四个任务队列:transientCallbacks、midFrameMicrotasks、persistentCallbacks、postFrameCallbacks,当四个任务队列执行完毕后当前 frame 结束。综上,Flutter 将整个生命周期分为五种状态,通过 SchedulerPhase 枚举类来表示它们:
enum SchedulerPhase {
/// 空闲状态,并没有 frame 在处理。这种状态代表页面未发生变化,并不需要重新渲染。
/// 如果页面发生变化,需要调用`scheduleFrame()`来请求 frame。
/// 注意,空闲状态只是指没有 frame 在处理,通常微任务、定时器回调或者用户事件回调都
/// 可能被执行,比如监听了tap事件,用户点击后我们 onTap 回调就是在idle阶段被执行的。
idle,
/// 执行”临时“回调任务,”临时“回调任务只能被执行一次,执行后会被移出”临时“任务队列。
/// 典型的代表就是动画回调会在该阶段执行。
transientCallbacks,
/// 在执行临时任务时可能会产生一些新的微任务,比如在执行第一个临时任务时创建了一个
/// Future,且这个 Future 在所有临时任务执行完毕前就已经 resolve 了,这中情况
/// Future 的回调将在[midFrameMicrotasks]阶段执行
midFrameMicrotasks,
/// 执行一些持久的任务(每一个frame都要执行的任务),比如渲染管线(构建、布局、绘制)
/// 就是在该任务队列中执行的.
persistentCallbacks,
/// 在当前 frame 在结束之前将会执行 postFrameCallbacks,通常进行一些清理工作和
/// 请求新的 frame。
postFrameCallbacks,
}
渲染管线就是在 persistentCallbacks 中执行的.
当新的 frame 到来时,调用到 WidgetsBinding 的 drawFrame() 方法
@override
void drawFrame() {
...//省略无关代码
try {
buildOwner.buildScope(renderViewElement); // 先执行构建
super.drawFrame(); //然后调用父类的 drawFrame 方法
}
}
实际上关键的代码就两行:先重新构建(build),然后再调用父类的 drawFrame 方法,将父类的 drawFrame方法展开后:
void drawFrame() {
buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget树
//下面是 展开 super.drawFrame() 方法
pipelineOwner.flushLayout(); // 2.更新布局
pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息
pipelineOwner.flushPaint(); // 4.重绘
if (sendFramesToEngine) {
renderView.compositeFrame(); // 5. 上屏,会将绘制出的bit数据发送给GPU
...
}
}
可以看到主要做了5件事:
- 重新构建widget树。
- 更新布局。
- 更新“层合成”信息。
- 重绘。
- 上屏:将绘制的产物显示在屏幕上
上面的5步为 rendering pipeline,中文翻译为 “渲染流水线” 或 “渲染管线”。而渲染管线的这 5 个步骤的具体过程便是本章重点要介绍的。
2、setState 执行流
setState 调用后:
- 首先调用当前 element 的 markNeedsBuild 方法,将当前 element标记为 dirty 。
- 接着调用 scheduleBuildFor,将当前 element 添加到pipelineOwner的 dirtyElements 列表。
- 最后请求一个新的 frame,随后会绘制新的 frame:onBuildScheduled->ensureVisualUpdate->scheduleFrame() 。当新的 frame 到来时执行渲染管线
void drawFrame() {
buildOwner!.buildScope(renderViewElement!); //重新构建widget树
pipelineOwner.flushLayout(); // 更新布局
pipelineOwner.flushCompositingBits(); //更新合成信息
pipelineOwner.flushPaint(); // 更新绘制
if (sendFramesToEngine) {
renderView.compositeFrame(); // 上屏,会将绘制出的bit数据发送给GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
以上,便是setState被调用到UI更的大概更新过程,实际的流程会更复杂一些,比如在build过程中是不允许再调用setState的,框架需要做一些检查。又比如在frame中会涉及到动画的调度、在上屏时会将所有的Layer添加到场景(Scene)对象后,再渲染Scene