阅读 169

四、build 流程分析

一、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、总结