1、build 构建流程
在监听 vsync 信号回调时会调用 drawFrame 函数
(1) drawFrame
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
superd.drawFrame();
}
在 drawFrame 方法中,则调用了 buildScope 则是 Build 流程
(2) buildScope
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
_dirtyElements[index].rebuild();
index += 1;
}
}
遍历 _dirtyElements, 重新 rebuild
(3)Element -> rebuild
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
rebuild 方法中主要是调用了 performRebuild
(4) Element -> performRebuild
这个类是由具体的类继承的,如以 Text widget 为例, 其 对应的 element 是 StatelessElement, 而 StatelessElement 继承 ComponentElement
(5)ComponentElement --> performRebuild
@override
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
(5)build
StatelessElement 重写 了 componentElement 的build 的方法
@override
Widget build() => widget.build(this);
由此可见,build 方法实际调用的就是我们 平时写组件时的 build,这个方法会返回新的 widget 树,新的widget 树有什么用呢?且看接下来调用的 updateChild
(6)updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null) {
deactivateChild(child); // 第一种情况
}
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第二种情况
}
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第三种情况
}
child.update(newWidget);
return child;
}
deactivateChild(child);
}
return inflateWidget(newWidget, newSlot); // 第四种情况
}
这个方法主要是涉及 Element 的更新逻辑,更新规则如下
第一种情况: build出来的widget等于null,也就是newWidget 是null, 说明这个控件被删除了,child Element可以被删除了
第二种情况:child的 widget 和新 build 出来的一样,则判断 slot(父级设置的信息,以定义此子级在其父级的子级列表中的位置) 是否一致,不一致则更新,Element还是旧的Element
第三种情况:canUpdate(判断key值和 runtimeType 是否一致) 为 true 时, 更新 Element 即可
第四种情况:以上三种情况都不满足时,创建新的Element
(7) inflateWidget
这个方法主要是用于创建新的 Element
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
// 寻找有没有可用的Element
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
// 创建 Element
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
创建了新的 Element 之后,则会调用 elment 的 mount 方法,完成 element 树的挂载,其中,mount 方法是由具体的子类实现的,ComponentElement 类的 mount 方法会递归遍历子节点,调用子节点的 rebuild方法: _firstBuild -> rebuild -> performRebuild ,而 RenderElement 类的 mount 方法 则会调用 widget 的 createRenderObject 创建对应的 renderObject, 并renderObject 插到对应的 render树上。
(8) ComponentElement --> mount
循环遍历子节点
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
(9) RenderElement --> mount
创建 renderObject 并且 将 renderObject 插入到 render 树上
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); // 创建renderObject
attachRenderObject(newSlot); // 将renderObject 插入到render树上
_dirty = false;
}
2、app 启动初始化构建流程
在Vsync 信号回调时会触发 build 流程,但是首帧渲染并没有等待 Vsync 信号的回调,即在 app 启动初始化时,element 树又是怎样构建的呢
(1) runApp
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
在 runApp 方法中,完成了 Binding 的初始化后,调用了 attachRootWidget 方法
(2)attachRootWidget
在这个方法中,通过 RenderObjectToWidgetAdapter 创建根elememnt _renderViewElement, 其中 renderView 是根 renderObject
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}
(3)attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
owner.buildScope(element, () {
element.mount(null, null);
});
return element;
}
其实这里也是将 根 element 挂载同时遍历创建子节点,这个流程跟上面分析的build流程是类似的