应用启动
Flutter的入口在"lib/main.dart"的main()函数中,它是Dart应用程序的起点。
void main() => runApp(MyApp());
查看runApp()方法
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
app是第一个组件,WidgetsFlutterBinding是绑定widget和Flutter引擎的桥梁。
WidgetsFlutterBinding
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
WidgetsFlutterBinding继承BindingBase,并且引入很多Binding,通过各种Binding源码可以看出基本上都是监听并处理Window对象的一些事件。WidgetsFlutterBinding.ensureInitialized()负责初始化一个WidgetsBinding的全局单例,然后调用scheduleAttachRootWidget方法将根Widget添加到RenderView。
attachRootWidget
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance.ensureVisualUpdate();
}
}
renderView是一个RenderObject,它是渲染树的根,而renderViewElement是renderView对应的Element对象,可见该方法主要完成了根widget到根 RenderObject再到根Element的整个关联过程。
attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
该方法负责创建根element,即RenderObjectToWidgetElement,并且将element与widget 进行关联,即创建出 widget树对应的element树。如果element 已经创建过了,则将根element 中关联的widget 设为新的,由此可以看出element 只会创建一次,后面会进行复用。那么BuildOwner是什么呢?其实它就是widget framework的管理类,它跟踪哪些 widget 需要重新构建。
组件树在构建(build)完毕后,回到runApp的实现中,当调用完attachRootWidget后,最后一行会调用 WidgetsFlutterBinding 实例的 scheduleWarmUpFrame() 方法,该方法的实现在SchedulerBinding 中,它被调用后会立即进行一次绘制,在此次绘制结束前,该方法会锁定事件分发,也就是说在本次绘制结束完成之前 Flutter 将不会响应各种事件,这可以保证在绘制过程中不会再触发新的重绘。
渲染管线
Frame
在 Flutter 中,frame 并不是每次屏幕刷新都会触发的,而是通过调用 window.scheduleFrame() 来主动请求进行渲染。Flutter 引擎会在合适的时机调用 onBeginFrame 和 onDrawFrame 回调,最终执行 drawFrame 进行渲染操作。因此,我们在 Flutter 中提到的 frame 指的是一次 drawFrame 的调用。
SchedulerPhase
Flutter 应用的执行过程可以分为 idle 和 frame 两种状态。在 idle 状态下,应用没有进行帧处理,如果应用状态发生变化需要刷新界面,则需要通过 scheduleFrame() 方法请求新的帧。当新的帧到来时,应用进入 frame 状态,此时会按照特定顺序执行四个任务队列:transientCallbacks、midFrameMicrotasks、persistentCallbacks 和 postFrameCallbacks。当这四个任务队列执行完毕后,当前帧的处理结束。
- idle:空闲状态,并没有 frame 在处理。这种状态代表页面未发生变化,并不需要重新渲染。
- transientCallbacks:执行”临时“回调任务,”临时“回调任务只能被执行一次,执行后会被移出”临时“任务队列。典型的代表就是动画回调会在该阶段执行。
- midFrameMicrotasks:在执行临时任务时可能会产生一些新的微任务,比如在执行第一个临时任务时创建了一个Future,且这个 Future 在所有临时任务执行完毕前就已经 resolve 了。
- persistentCallbacks: 执行一些持久的任务(每一个frame都要执行的任务),比如渲染管线(构建、布局、绘制)。
- postFrameCallbacks 在当前 frame 在结束之前将会执行 postFrameCallbacks,通常进行一些清理工作和请求新的 frame。
渲染管线(rendering pipeline)
@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树。
-
更新布局。
-
更新“层合成”信息。
-
重绘。
-
上屏:将绘制的产物显示在屏幕上
setState 执行流
执行setState调用以后
- 首先调用当前 element 的 markNeedsBuild 方法,将当前 element标记为 dirty 。
- 接着调用 scheduleBuildFor,将当前 element 添加到pipelineOwner的 dirtyElements 列表。
- 最后请求一个新的 frame,随后会绘制新的 frame:onBuildScheduled->ensureVisualUpdate->scheduleFrame() 。当新的 frame 到来时执行渲染管线
setState 执行时机问题
setState 会触发 build,而 build 是在执行 persistentCallbacks 阶段执行的,因此只要不是在该阶段执行 setState 就绝对安全,但是这样的粒度太粗,比如在transientCallbacks 和 midFrameMicrotasks 阶段,如果应用状态发生变化,最好的方式是只将组件标记为 dirty,而不用再去请求新的 frame ,因为当前frame 还没有执行到 persistentCallbacks,因此后面执行到后就会在当前帧渲染管线中刷新UI。因此,setState 在标记完 dirty 后会先判断一下调度状态,如果是 idle 或 执行 postFrameCallbacks 阶段才会去请求新的 frame :
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame(); // 请求新的frame
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks: // 注意这一行
return;
}
}
安全更新
void update(VoidCallback fn) {
final schedulerPhase = SchedulerBinding.instance.schedulerPhase;
if (schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(fn);
});
} else {
setState(fn);
}
}