四、build 流程分析

888 阅读3分钟

一、Flutter 之图像绘制原理

二、Widget、Element、RenderObject

三、Flutter UI 更新流程

五、layout 流程分析

六、Paint 绘制(1)

七、Paint 绘制(2)

八、composite 流程分析

九、Flutter 小实践

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流程是类似的

3、总结