相对于React Native
、Weex
等跨平台框架,Flutter
拥有自己的UI绘制体系,避免了React Native
、Weex
等跨平台框架与Native
系统的桥接,从而更好的提升了性能。
在Flutter
中,UI都是一帧一帧的绘制,但这绘制的背后都会经过如下阶段。
- 动画与微任务阶段,主要是处理动画及执行一系列微任务。
- 构建阶段(build),找出标记为“脏”的节点与布局边界之间的所有节点,并做相应的更新。
- 布局阶段,计算
Widget
的大小及位置的确定。 - compositingBits阶段,重绘之前的预处理操作,检查RenderObject是否需要重绘。
- 绘制阶段,根据
Widget
大小及位置来绘制UI。 - compositing阶段,将UI数据发送给GPU处理。
- semantics阶段,与平台的辅助功能相关。
- finalization阶段,主要是从
Element
树中移除无用的Element
对象及处理绘制结束回调。
下面就来分析上述的各个阶段
1、动画与微任务阶段
该阶段主要是处理动画及微任务。先来看动画的处理,在使用动画时,很多时候都会添加一个回调函数来进行状态获取或数据更新,如通过addListener
、addStatusListener
等函数来添加,而这些回调函数就会在本阶段来执行。具体是在SchedulerBinding
中的handleBeginFrame
函数中实现。
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.transientCallbacks;
//切换为transientCallbacks阶段
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);
});
_removedIds.clear();
} finally {
//切换为midFrameMicrotasks阶段
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
_invokeFrameCallback
就会调用在使用动画时注册的回调函数,这里仅执行一次。如果我们在运行时调用_invokeFrameCallback
函数的代码注释调,那么就无法获取动画的状态,从而影响动画的正确执行。
当回调函数执行完毕后,就会进入微任务阶段,在该阶段会执行一系列微任务,由于这涉及到Flutter
的异步任务体系,因此这里就不再叙述。
2、build阶段
在上一阶段执行完毕后,就进入build阶段,在该阶段主要是重新构建标记为“脏”的Widget
节点及将需要更新的RenderObject
对象标记为“脏”。
当handleBeginFrame
函数执行完毕后,就会执行handleDrawFrame
函数,该函数在SchedulerBinding
对象初始化时会与Window
相关联,所以除第一次需要主动调用外,其他时候皆是通过Window
来调用该函数。
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
//持久帧回调,该回调会一直存在,不会移除
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
//当前帧绘制完成回调
_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;
Timeline.finishSync(); // end the Frame
profile(() {
_profileFrameStopwatch.stop();
_profileFramePostEvent();
});
_currentFrameTimeStamp = null;
}
}
这里重点关注持久帧回调,该回调也是UI绘制的关键函数,是在RendererBinding
对象初始化时注册的。
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
...
//注册持久帧回调
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
//绘制帧
drawFrame();
}
//绘制帧
void drawFrame() {
//对Widget进行测量、布局
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
//对Widget进行绘制
pipelineOwner.flushPaint();
//发送数据给GPU
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
}
根据函数名可以发现并没有发现关于构建Widget
的相关函数,那么在何时构建尼?通过查看源码可以发现,在WidgetsBinding
中重写了drawFrame
函数。在该函数中会创建新的Widget
对象替换旧的Widget
对象并将不需要的Element
节点从树中移除。
@override
void drawFrame() {
...
try {
//如果根结点存在,就重新构建Widget
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
//调用RendererBinding中的drawFrame函数
super.drawFrame();
//移除不再使用的Element节点
buildOwner.finalizeTree();
} finally {...}
...
}
2.1、重新build Widget对象
Widget
对象的创建是在buildScope()
函数中实现的,这是一个非常重要的函数,具体实现如下。
void buildScope(Element context, [ VoidCallback callback ]) {
if (c