成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Flutter中的布局繪制流程簡析(一)

duan199226 / 1116人閱讀

摘要:入口界面的布局和繪制在每一幀都在發(fā)生著,甚至界面沒有變化,它也會存在可以想象每一幀里面,引擎都像流水線的一樣重復(fù)著幾個過程構(gòu)建控件樹,布局繪制和合成,周而復(fù)始。大概可以想到的主要功能負責管理那些,讓它們進行布局和繪制。

開始

Flutter對比前端流行的框架,除了構(gòu)建控件樹和控件狀態(tài)管理等,還多了布局和繪制的流程,布局和繪制以往都是前端開發(fā)可望而不可及的都被封鎖在瀏覽器渲染引擎的實現(xiàn)里面,而我們只能通過文檔或者做一些demo去深入,就像盲人摸象,很多時候都是只知其一不知其二。相對而言,F(xiàn)lutter把這個黑盒打開了,意味著我們可以做更加深入的優(yōu)化,開發(fā)效率也能成倍提高。
接下來就去深入去了解,盡可能把這個過程完整展現(xiàn)給大家。

入口

界面的布局和繪制在每一幀都在發(fā)生著,甚至界面沒有變化,它也會存在;可以想象每一幀里面,引擎都像流水線的一樣重復(fù)著幾個過程:build(構(gòu)建控件樹),layout(布局), paint(繪制)和 composite(合成),周而復(fù)始。那么驅(qū)動整個流水線的入口在哪里呢?
直接來到WidgetBinding.drawFrame方法:

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

這里renderViewElement就是Root了,在第一幀的時候,控件樹還沒有構(gòu)建,當然也不存在renderViewElement了;而接下來buildOwner這個對象是干嘛的呢?

BuilderOwner

先看一下從哪里開始會用到builderOwner的方法:

可以看到我們經(jīng)常使用setState方法就與BuilderOwner緊密關(guān)聯(lián)了,接著再看BuilderOwner.scheduleBuildFor方法:

void scheduleBuildFor(Element element) {
    ...
    if (element._inDirtyList) {
      ...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
   ...
  }

這里的處理過程:如果_scheduledFlushDirtyElements不為true,就調(diào)起onBuildScheduled方法,并把Elment都加入到_dirtyElements中,那么onBuildScheduled又會干些啥尼?
回到WidgetBinding.initInstances方法:

 void initInstances() {
    super.initInstances();
    ...
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    ...
  }

看到真實調(diào)用的是WidgetBinding._handleBuildScheduled方法,我們繼續(xù)完善剛才的調(diào)用過程:

所以這里就可以看到我們調(diào)用setState方法最終會觸發(fā)界面新的一幀繪制。

當觸發(fā)新的一幀時,我們又回到最初的WidgetBinding.drawFrame方法中,那么builderOwner.buildScope方法究竟會干些工作:

void buildScope(Element context, [VoidCallback callback]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    ..l
    Timeline.startSync("Build", arguments: timelineWhitelistArguments);
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        
        _dirtyElementsNeedsResorting = false;
        try {
          callback();
        } finally {
         ...
        }
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        ...
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            index -= 1;
          }
        }
      }
        ...
        return true;
      }());
    } finally {
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
    }
  }

首先把_scheduledFlushDirtyElements標記設(shè)為true,表示正在從新構(gòu)建新的控件樹,然后_dirtyElements會做一輪排序,看一下Element._sort的方法如何實現(xiàn)的:

static int _sort(Element a, Element b) {
    if (a.depth < b.depth)
      return -1;
    if (b.depth < a.depth)
      return 1;
    if (b.dirty && !a.dirty)
      return -1;
    if (a.dirty && !b.dirty)
      return 1;
    return 0;
  }

嗯,因為在這里最初排序都是標記為dirty的Element,所以最后的結(jié)果是,depth小的Element會排最前,depth大的排最后;也就是說父Element會比子Element更早被rebuild,這樣可以防止子Element會重復(fù)rebuild。
當在rebuild過程中有可能會加入新的Dirty Element,所以每次rebuild的時候都會重新檢查_dirtyElements是否有增加或者檢查_dirtyElementsNeedsResorting標記位,接著從新排序一遍,這個時候我們的_dirtyElements列表中就有可能存在之前已經(jīng)rebuild完,dirty為false的Element了,重新排序后,depth小的和dirty不為true的會排最前,重新把index定位到第一個Dirty Element繼續(xù)rebuild。
如果在這個過程我們想把已經(jīng)rebuild過一次的Element想重復(fù)加入到_dirtyElements中,形成死循環(huán),會怎樣的尼,這個時候Element._inDirtyList還是為true,表明Element已經(jīng)在_dirtyElements列表中,在開發(fā)模式下引擎會報錯,給出相應(yīng)提示;一般情況下是不應(yīng)該出現(xiàn)的,萬一出現(xiàn)就需要思考一下代碼是否合理了。

接著先跳過super.drawFrame方法,來到builderOwner.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();
    }
  }

主要把_inactiveElements都進行一次清理,所以使用GlobalKey的控件,如果想起到重用控件的效果,必須在同一幀里面完成“借用”,否則就會被清理了。

簡單總結(jié)一下BuilderOwner的功能就是:管理控件rebuild過程,讓控件有序的進行rebuild。

PipelineOwner

終于來到super.drawFrame方法,這個方法實際上調(diào)起的是RenderBinding.drawFrame方法:

void drawFrame() {
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }

我們又見到一個跟BuilderOwner名稱很相似的PipelineOwner,那PipelineOwner又起到什么樣的功能尼?直接深入
pipelineOwner.flushLayout方法:

void flushLayout() {
    Timeline.startSync("Layout", arguments: timelineWhitelistArguments);
    _debugDoingLayout = true;
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        final List dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = [];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      _debugDoingLayout = false;
      Timeline.finishSync();
    }
  }

跟builderOwner處理相似,先進行一次排序,depth小的排最前優(yōu)先處理,然后調(diào)起RenderObject._layoutWithoutResize方法。

暫時先整理一下,這個時候我們出現(xiàn)三個名詞:Widget,Element,RenderObject;它們的關(guān)系究竟是咋樣的尼,假設(shè)你熟悉前端的Vue或者React框架,它們的關(guān)系等同于下面這張圖:

也就是說RenderObject負責著界面的布局繪制和事件處理等;而Element則是進行virtual dom diff,并且負責創(chuàng)建RenderObject;Widget則是我們控件業(yè)務(wù)邏輯組織的地方, 負責創(chuàng)建Element。

大概可以想到PipelineOwner的主要功能:負責管理那些dirty render object,讓它們進行布局和繪制。

接著RenderObject._layoutWithoutResize方法:

void _layoutWithoutResize() {
    ...
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException("performLayout", e, stack);
    }
    ...
    _needsLayout = false;
    markNeedsPaint();
  }

可以看到其實直接調(diào)用了RenderObject.performLayout方法,而這個方法則是應(yīng)由開發(fā)者自己實現(xiàn)的布局邏輯,接著會調(diào)起RenderObject.markNeedsPaint方法,也就是說每次重新layout都會觸發(fā)一次paint。

void markNeedsPaint() {
    if (_needsPaint)
      return;
    _needsPaint = true;
    if (isRepaintBoundary) {
      if (owner != null) {
        owner._nodesNeedingPaint.add(this);
        owner.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      final RenderObject parent = this.parent;
      parent.markNeedsPaint();
    } else {
      if (owner != null)
        owner.requestVisualUpdate();
    }
  }

這里的邏輯,主要判斷當前的RenderObject.isRepaintBoundary是否為true,如果是則把當前RenderObject加入到PipelineOwner對應(yīng)的列表中等待接下來的flushPaint處理,并觸發(fā)下一幀的繪制;當isRepaintBoundary不為true的時候,則會一直往上查找直到找到isRepaintBoundary為true的RenderObject,也就是有可能會找到根節(jié)點RenderView,然后加入到_nodesNeedingPaint列表中:

class RenderView extends RenderObject with RenderObjectWithChildMixin {
    ...
    bool get isRepaintBoundary => true;
    ...
}

這樣的話我們就得注意了,如果經(jīng)常需要重繪區(qū)域,最好把isRepaintBoundary標記true,這樣就盡量避免觸發(fā)全局重繪,提高性能,對應(yīng)的flutter就已經(jīng)提供了一個RepaintBoundary控件,自動把isRepaintBoundary標記為true,非常方便我們?nèi)プ鰞?yōu)化。

既然有markNeedsPaint方法,當然也有markNeedsLayout方法:

void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        ...
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }

處理邏輯基本上跟markNeedsPaint差不多,_relayoutBoundary也可以減少全局重新布局,可以把布局范圍縮小,提高性能,但是_relayoutBoundary的設(shè)置是有點不一樣的,等會再去討論。

簡單整理一下

當我們用調(diào)起setState改變某些狀態(tài),例如:控件的高度;先回到BuilderOwner.buildScope,繼續(xù)dirty element的rebuild方法:

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

接著執(zhí)行performRebuild方法:

  void performRebuild() {
    Widget built;
    try {
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugReportException("building $this", e, stack);
      built = new ErrorWidget(e);
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      _debugReportException("building $this", e, stack);
      built = new ErrorWidget(e);
      _child = updateChild(null, built, slot);
    }
  }

控件會重新build出子控件樹,然后調(diào)起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);
  }

如果newWidget為null但是child不為null,也就是刪除原來的控件,就會調(diào)起deactivateChild方法,會把當前的Element加入到BuilderOwner._inactiveElements列表中(最后可能會被清除也可能會被重用)。

如果newWidget和child都不為null,也就是更新原來的控件,先調(diào)起Widget.canUpdate方法判斷是否能夠更新(一般都是根據(jù)Widget運行時類型是否相同來判斷),如果相同調(diào)起update方法,繼續(xù)更新的邏輯,如果不一樣,就要deactivate原來的控件,并且創(chuàng)建新的控件。

如果child為null而Widegt不為null,也就是要創(chuàng)建新的控件。

接下來會分別分析更新的邏輯和創(chuàng)建的邏輯:

更新

直接來到StatefulElement.update方法:

void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = _state._widget;
    _dirty = true;
    _state._widget = widget;
    try {
      _state.didUpdateWidget(oldWidget);
    } finally {
    }
    rebuild();
  }

這里首先會調(diào)起一個控件很重要的生命回調(diào)didUpdateWidget,綜合上述可以知道,這里是當新的子控件和舊的子控件類型一致時才會調(diào)起;接著就是子控件的rebuild過程,然后不停重復(fù)下去。

創(chuàng)建

直接來到Element.inflateWidget方法:

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;
  }

這里判斷key是否為GlobalKey,如果是會調(diào)起_retakeInactiveElement方法,目的是從Globalkey上重用控件,并把控件從BuilderOwner._inactiveElements列表上移除,防止它被unmount,接著就是從新跑一次updateChild流程;如果不是就在新的子控件上創(chuàng)建新的Element,并且mount上去。

但是如果多個child的時候是怎么更新的尼?
來到MultiChildRenderObjectElement.update方法:

void update(MultiChildRenderObjectWidget newWidget) {
    super.update(newWidget);
    _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
    _forgottenChildren.clear();
  }

框架里面好像只規(guī)定跟RenderObject相關(guān)的控件才可以支持多個child,而updateChildren就是一個flutter版本的virtual dom diff算法的實現(xiàn)。

剛才假設(shè)我們需要修改控件的高度,既然跟顯示有關(guān),必然跟RenderObejct相關(guān),直接來到RenderObjectElement.update方法:

void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }

最后調(diào)起的是RenderObjectWidget.updateRenderObject方法,在這里我們可以得到新創(chuàng)建的RenderObject,我們在這里把新的RenderObject的屬性賦值給舊的RenderObject,而在RenderObject相關(guān)屬性的setter方法中會調(diào)起markNeedsLayout方法,這樣在下一幀布局繪制的時候就會生效。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89613.html

相關(guān)文章

  • Flutter中的布局繪制流程簡析(二)

    摘要:所以這里為時把指向自身,因為自身的肯定符合約束的條件,也是提高布局效率的一個關(guān)鍵點。舉一個栗子,在中先讓布局之后,根據(jù)的,來設(shè)置自身的。意味著父控件要依賴子控件的,可能父控件的布局要根據(jù)子控件的來做調(diào)整。 布局約束 剛才所說的改變一個控件的高度,有時候并不像剛才所說只是改變一下屬性就能起作用,這里涉及到一個布局約束規(guī)則。直接看BoxConstraints的實現(xiàn),這個類主要定義了minW...

    icattlecoder 評論0 收藏0
  • Flutter之SchedulerBinding簡析

    摘要:但是接下來并不是討論單線程如何方便開發(fā),而是要深入的調(diào)度器,看一下是如何安排任務(wù),調(diào)度工作??偨Y(jié)在大部分情況下,其實并不用擔心會像游戲一樣瘋狂消耗電量,消耗電量表現(xiàn)應(yīng)該跟原生沒有多大差別。 開始 在原生開發(fā)中(例如Android)都會強調(diào)不能阻塞主線程,但是開發(fā)中經(jīng)常會遇到發(fā)送請求或者操作數(shù)據(jù)庫等,這些操作都會阻塞主線程,幾乎唯一辦法就是用多線程處理這些工作;而在Flutter中就像跟...

    BlackMass 評論0 收藏0
  • Flutter樣式和布局控件簡析()

    摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學可能都習慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...

    BoYang 評論0 收藏0
  • Flutter樣式和布局控件簡析()

    摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學可能都習慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...

    wangxinarhat 評論0 收藏0
  • Flutter樣式和布局控件簡析(二)

    摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深的體會。關(guān)于屬性,指前一個組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應(yīng)該會有更深...

    yck 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<