Flutter框架分析(七)-relayoutBoundary

1. 前言

Flutter框架分析(四)-RenderObject一文中,咱們簡單介紹了RenderObject中一個重要成員變量:RelayoutBoundary。下面咱們簡單回顧下RelayoutBoundary的主要做用。
當一個組件的大小被改變時,其parent的大小可能也會被影響,所以須要通知其父節點。若是這樣迭代上去,須要通知整棵RenderObject Tree從新佈局,必然會影響佈局效率。所以,Flutter經過RelayoutBoundaryRenderObject Tree分段,若是遇到了RelayoutBoundary,則不去通知其父節點從新佈局,由於其大小不會影響父節點的大小。這樣就只須要對RenderObject Tree中的一段從新佈局,提升了佈局效率。
那麼,RelayoutBoundary是怎麼實現將RenderObject Tree分段的呢?本文將經過源碼來剖析RelayoutBoundary的工做原理。node

2. 源碼解析

Flutter中,若是Widget有更新,須要從新佈局,Framework會將須要佈局的RenderObject加入PipelineOwner的_nodesNeedingLayout中,而後當下一個VSync信號來臨時,Framework會遍歷_nodesNeedingLayout,對其中的每個RenderObject從新進行佈局,遍歷_nodesNeedingLayout的函數源碼以下:markdown

void flushLayout() {
  try {
    // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
    while (_nodesNeedingLayout.isNotEmpty) {
      final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
      _nodesNeedingLayout = <RenderObject>[];
      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
        if (node._needsLayout && node.owner == this)
          node._layoutWithoutResize();
      }
    }
  } finally {
  }
}
複製代碼

其中,_layoutWithoutResize會調用RenderObjectperformLayout函數,實現該RenderObject的從新佈局。
以上流程的示意圖以下:架構

image.gif

由上述邏輯可知,當Widget有更新,須要從新佈局時,加入_nodesNeedingLayout的元素的多少直接關係到須要從新佈局元素的多少,若是能將盡量少的RenderObject加入_layoutWithoutResize,便可儘量提升佈局效率。這就是設計RelayoutBoundary的核心思路。
下面咱們來看何時會將RenderObject添加進_nodesNeedingLayout。從源碼能夠看到,添加進_nodesNeedingLayout有兩個地方:框架

  • 初始化RenderView的時候,源碼以下:
void scheduleInitialLayout() {
  _relayoutBoundary = this;
  owner._nodesNeedingLayout.add(this);
}
複製代碼

本函數只在Flutter初始化的時候調用一次。函數

  • RenderObject標記本身須要從新佈局的時候,源碼以下:
void markNeedsLayout() {
  if (_needsLayout) {
    return;
  }
  if (_relayoutBoundary != this) {
    markParentNeedsLayout();
  } else {
    _needsLayout = true;
    if (owner != null) {
      owner._nodesNeedingLayout.add(this);
      owner.requestVisualUpdate();
    }
  }
}
複製代碼

那本函數的調用時機是什麼呢?主要有如下幾種:oop

  • 子節點變更,例如attachdetach
  • 自身佈局變化,例如Size變化。

Flutter初始化進行第一次佈局,每一個RenderObject均須要佈局,所以無優化空間,本文主要關注對從新佈局的優化,即對markNeedsLayout的調用。接下來咱們分析markNeedsLayout的調用鏈。其流程圖以下:源碼分析

image.gif

可見,在一個RenderObject調用markNeedsLayout函數後,若是其自己不是_relayoutBoundary,則會經過markParentNeedsLayout函數調用到parentmarkNeedsLayout函數,從而造成遞歸調用,直到找到最近的一個是_relayoutBoundary的上級節點,纔會中止遞歸,並將該節點加入_nodesNeedingLayout。所以,經過_relayoutBoundary,FlutterRenderObject Tree劃分紅了數段,當位於某段的RenderObject須要從新佈局時,只會更新該段及其下的RenderObject,而不是整個RenderObject Tree。示意圖以下:佈局

image.gif

那麼,何時會將RenderObject設置爲RelayoutBoundary呢?知足如下4種狀況之一時,會將自身設置爲RelayoutBoundarypost

  • parentUsesSize = false:父節點的佈局不依賴當前節點的大小。
  • sizedByParent = true:當前節點大小由父節點決定。
  • constraints.isTight:大小爲肯定的值,即寬高的最大值等於最小值。
  • parent is not RenderObject:若是父節點不是RenderObject,子節點layout變化不須要通知父節點更新。

以上條件很好理解,例如parentUsesSize = false,此時父節點的佈局不依賴當前節點的大小,那當前節點佈局更新天然不須要通知父節點,所以能夠將其做爲一個RelayoutBoundary性能

3. 小結

本文首先介紹了RelayoutBoundary的做用,而後結合源碼分析了RelayoutBoundary的做用原理,其重點以下:

  1. RelayoutBoundary經過減小待佈局節點列表數量(加入_nodesNeedingLayout)的方式優化節點更新時的佈局效率。
  2. RelayoutBoundary的設置條件包括如下4種:
  • parentUsesSize = false
  • sizedByParent = true
  • constraints.isTight
  • parent is not RenderObjec

4. 參考文檔

如何在Flutter上實現高性能的動態模板渲染

5. 相關文章

Flutter框架分析(一)--架構總覽
Flutter框架分析(二)-- Widget
Flutter框架分析(三)-- Element
Flutter框架分析(四)-RenderObject
Flutter框架分析(五)-Widget,Element,RenderObject樹
Flutter框架分析(六)-Constraint
Flutter框架分析(八)-Platform Channel
Flutter框架分析- Parent Data
Flutter框架分析 -InheritedWidget

相關文章
相關標籤/搜索