flutter繪制流程——rebuild

rebuild是Element的方法,有兩種場(chǎng)景下會(huì)被調(diào)用:

  1. element第一次構(gòu)建mount的時(shí)候
  2. widget發(fā)生變化的時(shí)候
void rebuild() {
  if (_lifecycleState != _ElementLifecycle.active || !_dirty)
    return;
  performRebuild();
}

主要邏輯分為2步

  1. 判斷狀態(tài)是否是active,_dirty是否為true
  2. 執(zhí)行performRebuild()仑性,這是個(gè)抽象方法诬像,所以具體rebuild的邏輯由element子類(lèi)去實(shí)現(xiàn)

下面重點(diǎn)來(lái)看performRebuild()

performRebuild.png

1 performRebuild()

顧名思義真正執(zhí)行重新build的地方,因此每個(gè)實(shí)現(xiàn)類(lèi)會(huì)有所不同圃酵,下面看下不同類(lèi)型Element的實(shí)現(xiàn)

1.1 RenderObjectElement

更新renderObject泞莉,當(dāng)然還有一些RenderObjectElement的繼承類(lèi)可能還做了其他邏輯

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

1.1.1 SliverMultiBoxAdaptorElement

對(duì)于SliverGrid鼎姐,SliverList钾麸,ListView都會(huì)用到它,這里邏輯比較多炕桨,今天這篇不去細(xì)講饭尝,大概了解有關(guān)鍵邏輯 _build(index)和updateChild`,

@override
void performRebuild() {
  super.performRebuild();
  final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
  void processElement(int index) {
    //省略...
    final Element? newChild = updateChild(newChildren[index], _build(index), index);
    //省略...
  }
  //省略...
  newChildren.keys.forEach(processElement);
  //省略...
}

1.2 ComponentElement

void performRebuild() {
  Widget? built;
  try {
    //widget重建 如:StatelessElement
    built = build();
  } catch (e, stack) {
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
    //..省略
  }
}
  1. 執(zhí)行build()献宫,作為新的newWidget
  2. _child = updateChild(_child, built, slot);

1.2.1 StatelessElement

未覆寫(xiě)钥平,邏輯同ComponentElement的performRebuild()

1.2.2 StatefulElement

void performRebuild() {
  if (_didChangeDependencies) {
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}

在build之前判斷需要didChangeDependencies

總結(jié):performRebuild()實(shí)現(xiàn)分兩類(lèi),ComponentElement和RenderObjectElement

  • RenderObjectElement會(huì)updateRenderObject姊途,對(duì)于有child的繼承類(lèi)會(huì)進(jìn)行_build(index)和updateChild
  • ComponentElement的performRebuild主要分為兩步涉瘾。1:build(); 2:updateChild。下面依次展開(kāi)

2 build()

2.1 ComponentElement

/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();

用來(lái)build返回widget捷兰,這個(gè)我們就很熟悉了立叛,寫(xiě)UI代碼主要圍繞在這一塊

  1. StatelessElement調(diào)用widget.build(this);
  2. StatefulElement 調(diào)用state.build(this);
  3. ProxyElement 直接返回widget.child

2.2 SliverMultiBoxAdaptorElement

在1.1.1中的performRebuild()執(zhí)行的是_build

Widget? _build(int index) {
  return widget.delegate.build(this, index);
}

看到SliverChildDelegate中的定義,和ComponentElement的build意思差不多,只不是事根據(jù)傳入index來(lái)返回widget

/// Returns the child with the given index.
///
/// Should return null if asked to build a widget with a greater
/// index than exists. If this returns null, [estimatedChildCount]
/// must subsequently return a precise non-null value (which is then
/// used to implement [RenderSliverBoxChildManager.childCount]).
///
/// Subclasses typically override this function and wrap their children in
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
///
/// The values returned by this method are cached. To indicate that the
/// widgets have changed, a new delegate must be provided, and the new
/// delegate's [shouldRebuild] method must return true.
Widget? build(BuildContext context, int index);

總結(jié):不管是ComponentElement中的build贡茅,還是SliverMultiBoxAdaptorElement中的_build秘蛇,最終都是用來(lái)構(gòu)建一個(gè)Widget。

下面看到performRebuild的下一步updateChild顶考。

3 updateChild

3.1 Element/ComponentElement

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot)赁还,用來(lái)更新子element

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  //如果newWidget是null,并且old child非null驹沿,直接deactivateChild
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    //新舊widget相同的情況
    if (child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (Widget.canUpdate(child.widget, newWidget)) {
      //可以u(píng)pdate的情況艘策,也就是runtimetype和key相同
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      newChild = child;
    } else {
      //其他情況,移除舊的甚负,重新inflateWidget新的widget柬焕,會(huì)創(chuàng)建element
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    //old child是null审残,這里直接inflate新的widget,會(huì)創(chuàng)建element
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

總共分以下幾種情況

  1. newWidget是null斑举,則清理掉old child(如果old child不為null)搅轿,返回null
  2. 新舊widget相同,說(shuō)明數(shù)據(jù)沒(méi)有變化富玷,直接返回舊的child
  3. widget可以u(píng)pdate璧坟,child.update(newWidget); 直接更新old child即可
  4. 其他情況,重新inflateWidget赎懦,這里會(huì)createElement雀鹃,清理掉old child(如果old child不為null)

3.2 SliverMultiBoxAdaptorElement

RenderObjectElement的實(shí)現(xiàn)類(lèi)SliverMultiBoxAdaptorElement額外處理的就是更新child的renderobject的parentData

@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
  final Element? newChild = super.updateChild(child, newWidget, newSlot);
  final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;

  // Preserve the old layoutOffset if the renderObject was swapped out.
  if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
    newParentData.layoutOffset = oldParentData.layoutOffset;
  }
  return newChild;
}

下面看到update的邏輯

4 update

Element中定義,更新widget

@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}

4.1 StatelessElement

void update(StatelessWidget newWidget) {
  super.update(newWidget);
  _dirty = true;
  rebuild();
}

4.2 StatefulElement

void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = state._widget!;
  _dirty = true;
  state._widget = widget as StatefulWidget;
  state.didUpdateWidget(oldWidget) as dynamic;
  rebuild();
}

4.3 RenderObjectElement

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

下面看幾個(gè)典型的實(shí)現(xiàn)類(lèi)

4.3.1 SingleChildRenderObjectElement

@override
void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  _child = updateChild(_child, widget.child, null);
}

4.3.2 MutliChildRenderObjectElement

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

總結(jié):對(duì)于update方法首先一定會(huì)做的就是更新_widget励两。然后對(duì)于ComponentElement和RenderObjectElement的邏輯有所不同黎茎。

  • ComponentElement主要會(huì)進(jìn)行rebuild();這樣又回到最初的rebuild,只是到了子節(jié)點(diǎn)
  • RenderObjectElement則會(huì)更新自己的renderObject当悔,然后根據(jù)擁有child是否是多個(gè)邏輯有所不同傅瞻,如:
    • SingleChildRenderObjectElement只有一個(gè)child,執(zhí)行updateChild 這樣也回到了前面的步驟3
    • MutliChildRenderObjectElement可能有多個(gè)child盲憎,執(zhí)行updateChildren

下面重點(diǎn)開(kāi)看updateChildren

5 updateChildren

RenderObjectElement中定義嗅骄,針對(duì)可能有多個(gè)child的element的更新邏輯

5.1 定義新舊children的開(kāi)始和結(jié)束位置,用于后面遍歷

//定義old和new的首尾位置
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
//根據(jù)old和new的長(zhǎng)度判斷饼疙,如果相同則newChildren直接使用oldChildren溺森,如果不同,則創(chuàng)建一個(gè)長(zhǎng)度為newWidgets.length的list窑眯,使用_NullElement.instance來(lái)填充
final List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);

5.2 從開(kāi)始位置遍歷children屏积,主要處理可以直接updateChild的情況,碰到不能update則直接跳出磅甩,newChildrenTop和oldChildrenTop會(huì)指向不能update的child位置

// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  //判斷oldChild是否被移除
  final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
  final Widget newWidget = newWidgets[newChildrenTop];
  //oldChild是空或者newWidget不能直接更新肾请,就跳出
  if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
    break;
  //更新child
  final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  //設(shè)置到newChild中
  newChildren[newChildrenTop] = newChild;
  //設(shè)置上一個(gè)child
  previousChild = newChild;
  //移動(dòng)到下一個(gè)位置
  newChildrenTop += 1;
  oldChildrenTop += 1;
}

5.3 從底部開(kāi)始遍歷判斷canUpdate,知道返回false更胖,跳出铛铁,這樣oldChildrenBottom和newChildrenBottom指向末尾不能update的child

// 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;
  //注意這里和step2的區(qū)別是沒(méi)有去設(shè)置previousChild了,并且沒(méi)有updateChild
  oldChildrenBottom -= 1;
  newChildrenBottom -= 1;
}

5.4 從oldChildrenTop開(kāi)始遍歷oldChildren却妨,取出widget.key不為null的child饵逐,存入oldKeyedChildren,后面可能取出進(jìn)行復(fù)用彪标,這里oldChildrenTop應(yīng)該等于oldChildrenBottom+1

// Scan the old children in the middle of the list.
// 根據(jù)top和bottom位置判斷是否還存在中間的元素沒(méi)有處理
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
//用于存放有key的old child
Map<Key, Element>? oldKeyedChildren;
if (haveOldChildren) {
  oldKeyedChildren = <Key, Element>{};
  //從頂部開(kāi)始遍歷oldChildren
  while (oldChildrenTop <= oldChildrenBottom) {
    final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
    if (oldChild != null) {
      //如果有key倍权,則存入oldKeyedChildren
      if (oldChild.widget.key != null)
        oldKeyedChildren[oldChild.widget.key!] = oldChild;
      else
        //沒(méi)有直接廢棄oldChild
        deactivateChild(oldChild);
    }
    //注意:這里是old child的位置移動(dòng)
    oldChildrenTop += 1;
  }
}

5.5 從newChildrenTop開(kāi)始遍歷newWidgets,根據(jù)key從oldKeyedChildren取出old child,然后判斷是否可以直接update薄声,如果可以則在updateChild的作為oldChild參數(shù)傳入当船,否則傳null。到這里newChildrenTop應(yīng)該等于newChildrenBottom+1

// Update the middle of the list.
//從頂部更新newChildren
while (newChildrenTop <= newChildrenBottom) {
  Element? oldChild;
  final Widget newWidget = newWidgets[newChildrenTop];
  if (haveOldChildren) {
    final Key? key = newWidget.key;
    //判斷new child是否有key
    if (key != null) {
      //獲取old child有相同key的child
      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, slotFor(newChildrenTop, previousChild))!;
  newChildren[newChildrenTop] = newChild;
  previousChild = newChild;
  newChildrenTop += 1;
}

5.6 在4.3中只是做了newChildrenBottom和oldChildrenBottom的標(biāo)記默辨,并沒(méi)有真正的updateChild德频,所以。下面重置newChildrenBottom和oldChildrenBottom缩幸。繼續(xù)從oldChildrenTop開(kāi)始遍歷壹置,然后updateChild

// We've scanned the whole list.
//重置bottom位置
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];
  //更新剩余的child
  final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  newChildren[newChildrenTop] = newChild;
  previousChild = newChild;
  newChildrenTop += 1;
  oldChildrenTop += 1;
}

5.7 清理沒(méi)有復(fù)用成功的child

  // Clean up any of the remaining middle nodes from the old list.
  if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
    for (final Element oldChild in oldKeyedChildren.values) {
      //將剩下帶有key的old child,同時(shí)又沒(méi)能復(fù)用的child進(jìn)行clean
      if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
        deactivateChild(oldChild);
    }
  }
  return newChildren;
}

總結(jié):這么長(zhǎng)的流程和邏輯主要是為了對(duì)多個(gè)children的情況要進(jìn)行判斷是否可以復(fù)用表谊,對(duì)于不能復(fù)用的child進(jìn)行清理钞护,最終針對(duì)child還是會(huì)執(zhí)行到updateChild這樣又回到了3

6 inflateWidget

Element中定義,在第3節(jié)中 updateChild如果old child是空或者無(wú)法update就需要inflateWidget

寫(xiě)android的朋友應(yīng)該很熟悉了爆办,android里有LayoutInflater.from().inflate(),從xml來(lái)解析獲取到View难咕;同樣在這里通過(guò)widget來(lái)解析返回Element。關(guān)于GlobalKey的邏輯距辆,我們先忽略步藕,后面再介紹。下面的邏輯就簡(jiǎn)單了挑格,創(chuàng)建一個(gè)element,然后mount到當(dāng)前element

Element inflateWidget(Widget newWidget, Object? 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!;
    }
  }
  //創(chuàng)建子Element
  final Element newChild = newWidget.createElement();
  //將child掛載到當(dāng)前element
  newChild.mount(this, newSlot);
  //返回child element
  return newChild;
}

7 總結(jié)

走完上面整個(gè)rebuild流程沾歪,第一感受就是在于ComponentElement和RenderObjectElement在流程上有明顯的區(qū)別漂彤,這也回到這兩類(lèi)Element的設(shè)計(jì),RenderObjectElement不一定包含子child灾搏,但它包括renderObject用于渲染挫望,而ComponentElement是一種組成的Element,它并不包含RenderObject狂窑,但它會(huì)有子 Element媳板。因此在rebuild時(shí)ComponentElement只需要關(guān)心child的update,而RenderObjectElement還需要關(guān)注RenderObject的更新泉哈。另外在多child的情況如:第5節(jié)蛉幸,diff的邏輯會(huì)稍微復(fù)雜一點(diǎn)。

對(duì)于整個(gè)流程中的關(guān)于方法我們也要熟悉丛晦,如:update奕纫,inflateWidget,updateChild烫沙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匹层,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锌蓄,更是在濱河造成了極大的恐慌升筏,老刑警劉巖撑柔,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異您访,居然都是意外死亡铅忿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)洋只,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)辆沦,“玉大人,你說(shuō)我怎么就攤上這事识虚≈叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵担锤,是天一觀的道長(zhǎng)蔚晨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)肛循,這世上最難降的妖魔是什么铭腕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮多糠,結(jié)果婚禮上累舷,老公的妹妹穿的比我還像新娘。我一直安慰自己夹孔,他們只是感情好被盈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著搭伤,像睡著了一般只怎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怜俐,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天身堡,我揣著相機(jī)與錄音,去河邊找鬼拍鲤。 笑死贴谎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的季稳。 我是一名探鬼主播赴精,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绞幌!你這毒婦竟也來(lái)了蕾哟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谭确,沒(méi)想到半個(gè)月后帘营,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逐哈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年芬迄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昂秃。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禀梳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肠骆,到底是詐尸還是另有隱情算途,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布蚀腿,位于F島的核電站嘴瓤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莉钙。R本人自食惡果不足惜廓脆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磁玉。 院中可真熱鬧停忿,春花似錦、人聲如沸蚊伞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厚柳。三九已至,卻和暖如春沐兵,著一層夾襖步出監(jiān)牢的瞬間别垮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工扎谎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碳想,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓毁靶,卻偏偏與公主長(zhǎng)得像胧奔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子预吆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容