4、build 流程分析

1、Flutter 之圖像繪製原理bash

2、Widget、Element、RenderObjectapp

3、Flutter UI 更新流程less

5、layout 流程分析ide

6、Paint 繪製(1)函數

7、Paint 繪製(2)post

8、composite 流程分析ui

9、Flutter 小實踐this

一、build 構建流程

在監聽 vsync 信號回調時會調用 drawFrame 函數spa

(1) drawFramedebug

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;
}
複製代碼

二、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流程是相似的

三、總結

相關文章
相關標籤/搜索