Flutter Widget框架-渲染原理解析

Flutter Framework

視圖樹的建立與管理機制、佈局、渲染核心框架node

視圖樹

  • Widget => 爲Element提供配置信息
  • Element => Flutter建立Element的可見樹, 同時持有Widget和RenderObject
  • RenderObject => 渲染樹中的一個對象

渲染機制

調用runApp(rootWidget),將rootWidget傳給rootElement,作爲rootElement的子節點,生成Element樹,由Element樹生成Render樹算法

runApp(首次執行)

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製代碼

runApp(rootWidget) => attachRootWidget(rootWidget) => attachToRenderTree() => element.mount() => _rebuild() => updateChild()緩存

1. WidgetsFlutterBinding

WidgetsFlutterBinding混入了很多的其餘的Bindingbash

  • BindingBase 那些單一服務的混入類的基類
  • GestureBinding framework手勢子系統的綁定,處理用戶輸入事件
  • ServicesBinding 接受平臺的消息將他們轉換成二進制消息,用於平臺與flutter的通訊
  • SchedulerBinding 調度系統,用於調用Transient callbacks(Window.onBeginFrame的回調)、Persistent callbacks(Window.onDrawFrame的回調)、Post-frame callbacks(在Frame結束時只會被調用一次,調用後會被系統移除,在Persistent callbacks後Window.onDrawFrame回調返回以前執行)
  • PaintingBinding 繪製庫的綁定,主要處理圖片緩存
  • SemanticsBinding 語義化層與Flutter engine的橋樑,主要是輔助功能的底層支持
  • RendererBinding 渲染樹與Flutter engine的橋樑
  • WidgetsBinding Widget層與Flutter engine的橋樑

持有BuildOwner、PipelineOwnerapp

  • BuildOwner框架

    BuildOwner是Widget framework的管理類, 該類跟蹤哪些小部件須要從新構建,並處理應用於整個小部件樹的其餘任務,好比管理樹的非活動元素列表less

  • PipelineOwneride

    管理真正須要繪製的View, 對RenderObjectTree中發生變化節點的進行flush操做, 最後交給底層引擎渲染佈局

2. attachRootWidget

  • 1.attachRootWidget(app) 方法建立了Root[Widget](也就是 RenderObjectToWidgetAdapter)ui

    void attachRootWidget(Widget rootWidget) {
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
          container: renderView,
          debugShortDescription: '[root]',
          child: rootWidget
        ).attachToRenderTree(buildOwner, renderViewElement);
      }
    複製代碼
  • 2.緊接着調用attachToRenderTree方法建立了 Root[Element]

    RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
        if (element == null) {
          owner.lockState(() {
            element = createElement();  //建立rootElement
            element.assignOwner(owner); //綁定BuildOwner
          });
          owner.buildScope(element, () { //子widget的初始化從這裏開始
            element.mount(null, null);  // 初始化子Widget前,先執行rootElement的mount方法
          });
        } else {
          ...
        }
        return element;
      }
    複製代碼
  • 3.Root[Element]嘗試調用mount方法將本身掛載到父Element上,由於本身就是root了,因此沒有父Element,掛空了

    owner.buildScope(element, () { //子widget的初始化從這裏開始
        element.mount(null, null);  // 初始化子Widget前,先執行rootElement的mount方法
      });
    複製代碼
  • 4.mount的過程當中會調用Widget的createRenderObject,建立了 Root[RenderObject]

    void mount(Element parent, dynamic newSlot) {
        _parent = parent; //持有父Element的引用
        _slot = newSlot;
        _depth = _parent != null ? _parent.depth + 1 : 1;//當前節點的深度
        _active = true;
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner; //每一個Element的buildOwner,都來自父類的BuildOwner, 這樣能夠保證一個ElementTree,只由一個BuildOwner來維護
        ...
      }
      
    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        _renderObject = widget.createRenderObject(this);
        attachRenderObject(newSlot);
        _dirty = false;
      }
    複製代碼
  • 5.咱們將app做爲參數傳給了Root[Widget](也就是 RenderObjectToWidgetAdapter),app[Widget]也就成了爲root[Widget]的child[Widget]

  • 6.調用owner.buildScope,開始執行子Tree的建立以及掛載(與更新流程一致, 見更新)

  • 7.調用createElement方法建立出Child[Element]

    Element inflateWidget(Widget newWidget, dynamic newSlot) {
        final Key key = newWidget.key;
    
        if (key is GlobalKey) {
          final Element newChild = _retakeInactiveElement(key, newWidget);
          if (newChild != null) {
    
            newChild._activateWithParent(this, newSlot);
            final Element updatedChild = updateChild(newChild, newWidget, newSlot);
            return updatedChild;
          }
        }
        final Element newChild = newWidget.createElement();
        newChild.mount(this, newSlot);
        return newChild;
      }
    複製代碼
  • 8.調用Element的mount方法,將本身掛載到Root[Element]上,造成一棵樹

  • 9.掛載的同時,調用widget.createRenderObject,建立Child[RenderObject]

  • 10.建立完成後,調用attachRenderObject,完成和Root[RenderObject]的連接

    @override
      void attachRenderObject(dynamic newSlot) {
        _slot = newSlot;
        
        _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
        _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
        
        final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
        if (parentDataElement != null)
          _updateParentData(parentDataElement.widget);
      }
    複製代碼

    RenderObject與父RenderObject的掛載稍微複雜了點。每個Widget都有一個對應的Element,但Element不必定會有對應的RenderObject。(由於有一些Element是不用來作頁面顯示的, 像StatelessWidget=>StatelessElement沒有對應的RenderObject)因此你的父Element並不一有RenderObject,這個時候就須要向上查找。

    RenderObjectElement _findAncestorRenderObjectElement() {
        Element ancestor = _parent;
        while (ancestor != null && ancestor is! RenderObjectElement)
          ancestor = ancestor._parent;
        return ancestor;
      }
    複製代碼

    find方法在向上遍歷Element,直到找到RenderObjectElement,RenderObjectElement確定是有對應的RenderObject了,這個時候在進行RenderObject子父間的掛載。

3. scheduleWarmUpFrame

安排一個幀儘快運行, 這在應用程序啓動期間使用,以便第一個幀(可能很是昂貴)能夠多運行幾毫秒。(我的理解是爲了實現第一次頁面渲染能夠調用到 => drawFrame)

setState(更新)

@protected
void setState(VoidCallback fn) {
	...
	_element.markNeedsBuild();
}
複製代碼

1.Element標記自身爲dirty,並通知buildOwner處理

void markNeedsBuild() {
	...
    _dirty = true; // 標記自身爲dirty
    owner.scheduleBuildFor(this); // 通知buildOwner處理
  }
複製代碼

2.buildOwner將element添加到集合_dirtyElements中,並通知ui.window安排新的一幀

buildOwner會將全部dirty的Element添加到_dirtyElements當中,等待下一幀繪製時集中處理。

還會調用ui.window.scheduleFrame();通知底層渲染引擎安排新的一幀處理。

void scheduleBuildFor(Element element) {
    ...
    _dirtyElements.add(element);
    element._inDirtyList = true;
	...
  }
複製代碼

3.底層引擎最終回調到Dart層, 完成計算渲染回收

void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
    }
	...
}
複製代碼

3.1 執行buildOwner的buildScope方法

void buildScope(Element context, [VoidCallback callback]) {
    ...
    try {
		...
		//1.排序
      _dirtyElements.sort(Element._sort);
     	...
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        try {
        	//2.遍歷rebuild
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
        }
        index += 1;
      }
    } finally {
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      //3.清空
      _dirtyElements.clear();
		...
    }
  }
複製代碼
3.1.1 按照Element的深度從小到大,對_dirtyElements進行排序
3.1.2 遍歷執行_dirtyElements當中element的rebuild方法

遍歷執行的過程當中,也有可能會有新的element被加入到_dirtyElements集合中,此時會根據dirtyElements集合的長度判斷是否有新的元素進來了,若是有,就從新排序。

void rebuild() {
   
    if (!_active || !_dirty)
      return;

    Element debugPreviousBuildTarget;

    performRebuild();
  }
複製代碼

element的rebuild方法最終會調用performRebuild(),而performRebuild()不一樣的Element有不一樣的實現

執行performRebuild()

performRebuild()不一樣的Element有不一樣的實現,咱們暫時只看最經常使用的兩個Element:

  • ComponentElement,是StatefulWidget和StatelessElement的父類

    void performRebuild() {
        Widget built;
        try {
          built = build();
        } 
        ...
        try {
          _child = updateChild(_child, built, slot);
        } 
        ...
      }
    複製代碼

    build()執行咱們複寫的StatefulWidget的state的build方法, 拿到子Widget, 交給updateChild

    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    	...
    		//1. 若是newWidget是null, 說明刪除控件, Element被刪除
        if (newWidget == null) {
          if (child != null)
            deactivateChild(child);
          return null;
        }
        
        if (child != null) {
        	//2. 若是新舊控件相同, 說明Widget複用了, 判斷位置是否相同, 不相同更新
          if (child.widget == newWidget) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            return child;
          }
          //3. 判斷key值和運行時類型(runtimeType)是否相等, 都相同才能夠更新, 更新並返回Element(這個時候應該是Widget變了, 可是仍是同類型的Widget)
          if (Widget.canUpdate(child.widget, newWidget)) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            child.update(newWidget);// 
            return child;
          }
          deactivateChild(child);
        }
        //4. 若是上面的條件都不知足, 建立新的Element
        return inflateWidget(newWidget, newSlot);
      }
    複製代碼
    • 1.若是newWidget是null, 說明刪除控件, Element被刪除

    • 2.若是新舊控件相同, 說明Widget複用了, 判斷位置是否相同, 不相同更新

    • 3.判斷key值和運行時類型(runtimeType)是否相等, 都相同才能夠更新, 更新並返回Element(這個時候應該是Widget變了, 可是仍是同類型的Widget)

      static bool canUpdate(Widget oldWidget, Widget newWidget) {
          return oldWidget.runtimeType == newWidget.runtimeType
              && oldWidget.key == newWidget.key;
        }
      複製代碼

      child.update(newWidget);方法, 會根據newWidget的類型執行不一樣的update方法, 例如:

      • Column是MultiChildRenderObjectWidget類型的, 就會執行下面的方法:

        @override
          void update(MultiChildRenderObjectWidget newWidget) {
        
            super.update(newWidget);
            _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
            _forgottenChildren.clear();
          }
        複製代碼

        因爲Column裏面的孩子是children類型(MultiChildRenderObjectWidget), 有多個, 因此對比算法採用updateChildren, 返回新的Element

      • Container是StatelessWidget類型的, 因此他執行StatelessWidget的update方法:

        @override
          void update(StatelessWidget newWidget) {
            super.update(newWidget);
            _dirty = true;
            rebuild();
          }
        複製代碼
      • Scaffold是StatefulWidget類型的, 因此執行:

        @override
          void update(StatefulWidget newWidget) {
        
            super.update(newWidget);
        
            final StatefulWidget oldWidget = _state._widget;
         
            _dirty = true;
            _state._widget = widget;
            try {
              _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
              final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
            } finally {
              _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
            }
            rebuild();
          }
        複製代碼

      若是是StatelessWidget/StatefulWidget類型, 則繼續執行下一級的對比, 以此類推.(child.update => rebuild => performRebuild(), rebuild就是上面那個方法)

      若是是MultiChildRenderObjectWidget類型, 則updateChildren裏面會進行List對比算法, 每個item也會調用updateChild()方法, 進行計算, 詳細過程見最後

    • 4.若是上面的條件都不知足, 建立新的Element

      首先會嘗試經過GlobalKey去查找可複用的Element,複用失敗就調用Widget的方法建立新的Element,而後調用mount方法,將本身掛載到父Element上去,會在這個方法裏建立新的RenderObject。

      Element inflateWidget(Widget newWidget, dynamic newSlot) {
          final Key key = newWidget.key;
          if (key is GlobalKey) {
            final Element newChild = _retakeInactiveElement(key, newWidget);
            if (newChild != null) {
              newChild._activateWithParent(this, newSlot);
              final Element updatedChild = updateChild(newChild, newWidget, newSlot);
              return updatedChild;
            }
          }
          final Element newChild = newWidget.createElement();
          newChild.mount(this, newSlot);
          return newChild;
        }
      複製代碼
  • RenderObjectElement,是有渲染功能的Element的父類

    與ComponentElement的不一樣之處在於,沒有去build,而是調用了updateRenderObject方法更新RenderObject。

    @override
      void performRebuild() {
        widget.updateRenderObject(this, renderObject);
        _dirty = false;
      }
    複製代碼

    他表明的具備本身渲染功能的一類Widget(Text,沒有child的)

3.1.3 遍歷結束以後,清空dirtyElements集合

3.2 執行WidgetsBinding 的drawFrame (), PipelineOwner對RenderObject管理, 更新頁面

@protected
  void drawFrame() {
    pipelineOwner.flushLayout();  //佈局須要被佈局的RenderObject
    pipelineOwner.flushCompositingBits(); // 判斷layer是否變化
    pipelineOwner.flushPaint();  //繪製須要被繪製的RenderObject
    renderView.compositeFrame(); // this sends the bits to the GPU 將畫好的layer傳給engine繪製
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. 一些語義場景須要
  }
複製代碼

3.3 執行了buildOwner.finalizeTree()清理

void finalizeTree() {
    Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
    try {
      lockState(() {
        _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
      });
     ...
    } catch (e, stack) {
      _debugReportException('while finalizing the widget tree', e, stack);
    } finally {
      Timeline.finishSync();
    }
  }
複製代碼

全部沒用的element都調用了deactivateChild方法進行回收

void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements.add(child); // this eventually calls child.deactivate()
  }

複製代碼

也就在這裏將被廢棄的element添加到了_inactiveElements當中。

另外在廢棄element以後,調用inflateWidget建立新的element時,還調用了_retakeInactiveElement嘗試經過GlobalKey複用element,此時的複用池也是在_inactiveElements當中。

若是你沒有在一幀裏經過GlobeKey完成Element的複用,_inactiveElements在最後將被清空,就沒辦法在複用了。

updateChildren詳細過程

@protected
  List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> forgottenChildren }) {

    Element replaceWithNullIfForgotten(Element child) {
      return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
    }

    int newChildrenTop = 0;
    int oldChildrenTop = 0;
    int newChildrenBottom = newWidgets.length - 1;
    int oldChildrenBottom = oldChildren.length - 1;

    final List<Element> newChildren = oldChildren.length == newWidgets.length ?
        oldChildren : List<Element>(newWidgets.length);

    Element previousChild;

    // Update the top of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      final Widget newWidget = newWidgets[newChildrenTop];
     
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      final Element newChild = updateChild(oldChild, newWidget, previousChild);
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // Scan the bottom of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
      final Widget newWidget = newWidgets[newChildrenBottom];
      
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      oldChildrenBottom -= 1;
      newChildrenBottom -= 1;
    }

    // Scan the old children in the middle of the list.
    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
    Map<Key, Element> oldKeyedChildren;
    if (haveOldChildren) {
      oldKeyedChildren = <Key, Element>{};
      while (oldChildrenTop <= oldChildrenBottom) {
        final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
        
        if (oldChild != null) {
          if (oldChild.widget.key != null)
            oldKeyedChildren[oldChild.widget.key] = oldChild;
          else
            deactivateChild(oldChild);
        }
        oldChildrenTop += 1;
      }
    }

    // Update the middle of the list.
    while (newChildrenTop <= newChildrenBottom) {
      Element oldChild;
      final Widget newWidget = newWidgets[newChildrenTop];
      if (haveOldChildren) {
        final Key key = newWidget.key;
        if (key != null) {
          oldChild = oldKeyedChildren[key];
          if (oldChild != null) {
            if (Widget.canUpdate(oldChild.widget, newWidget)) {
              // we found a match!
              // remove it from oldKeyedChildren so we don't unsync it later oldKeyedChildren.remove(key); } else { // Not a match, let's pretend we didn't see it for now. oldChild = null; } } } } final Element newChild = updateChild(oldChild, newWidget, previousChild); newChildren[newChildrenTop] = newChild; previousChild = newChild; newChildrenTop += 1; } // We've scanned the whole list.
    newChildrenBottom = newWidgets.length - 1;
    oldChildrenBottom = oldChildren.length - 1;

    // Update the bottom of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = oldChildren[oldChildrenTop];
      
      final Widget newWidget = newWidgets[newChildrenTop];
      
      final Element newChild = updateChild(oldChild, newWidget, previousChild);
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // Clean up any of the remaining middle nodes from the old list.
    if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
      for (Element oldChild in oldKeyedChildren.values) {
        if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
          deactivateChild(oldChild);
      }
    }

    return newChildren;
  }
複製代碼
  1. 從前日後, 依次判斷oldChild是否存在(若是是GlobalKey, 則當作不存在處理), 而且新舊節點是否相同, 皆是則執行updateChild對比裏面的節點, 返回Element, 賦值給newChildren, 記錄下一個沒有對比的新舊節點序號; 若是不一樣, 則跳出循環.
  2. 從後往前, 依次判斷oldChild是否存在, 而且新舊節點是否相同, 皆是則記錄下一個沒有對比的新舊節點序號; 若是不一樣, 則跳出循環.
  3. 判斷是否還有未比較的oldChildren, 若是有, 則獲取到全部含有key的節點, 存入oldKeyedChildren, 不包括GlobalKey.
  4. 循環未比較的newChildren, 是否存在key, 存在則對比key是否在oldKeyedChildren中存在, 存在則移除oldKeyedChildren中對於的key, 最後調用updateChild返回Element, 賦值給newChildren.
  5. 再次循環, 把後面具備相同key的數據賦值給newChildren.
  6. 刪除多餘的oldChildren.
相關文章
相關標籤/搜索