二、Flutter的渲染機制以及setState()背后的原理

1.setState()干了啥?

@protected
void setState(VoidCallback fn) {         
  //調(diào)用我們傳入的函數(shù)
  final dynamic result = fn() as dynamic;
  //當前的element執(zhí)行markNeedsBuild方法
  _element.markNeedsBuild();
}

/// owner The object that manages the lifecycle of this element.
/// BuildOwner owner負責管理所有element的構(gòu)建以及生命周期
void markNeedsBuild() {
  //element將自己標記為臟
  _dirty = true;
  //scheduleBuildFor 譯:計劃構(gòu)建
  owner.scheduleBuildFor(this);
}

void scheduleBuildFor(Element element) {
  onBuildScheduled!();
  //將element添加到BuildOwner的_dirtyElements集合中
  _dirtyElements.add(element);
  //當前element已在臟列表中屬性設(shè)置為true
  element._inDirtyList = true;
}

  • 小結(jié):setState()就是將當前的element標記成然后交由BuildOwner,并加入到BuildOwner_dirtyElements臟列表中。

2.那頁面是如何更新的呢?

@override
void drawFrame() {
try {
    if (renderViewElement != null)
      // buildOwner就是前面提到的負責管理widgetbuild的對象
      // This is initialized the first time [runApp] is called.
      // 這里的renderViewElement是整個UI樹的根節(jié)點
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
        //將不再活躍的節(jié)點從UI樹中移除
    buildOwner.finalizeTree();
  } finally {
        /·················/
  }
}

void buildScope(Element context, [ VoidCallback callback ]) {
...
//取出臟elements調(diào)用rebuild() rebuild方法最終會觸發(fā)performRebuild
   final Element element = _dirtyElements[index];
   element.rebuild();
...
}

void performRebuild() {
  Widget built;
//這里解釋了為啥setState會觸發(fā)build方法
  built = build();

//執(zhí)行updateChild操作(即更新得到最新的子節(jié)點)
//_child為當前element的子element,built則是build后element最新當前的widget
  _child = updateChild(_child, built, slot);
}

//setState會觸發(fā)build方法的原因
class StatefulElement extends ComponentElement {
...
Widget build() => state.build(this);
...

//child為子element newWidget為re->build后新的當前element的widget
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
//新的子節(jié)點是空的 老的是有子節(jié)點 則清除子節(jié)點即可
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }

//??新的節(jié)點和老的是同一個Widget實例 則return newChild;
if (hasSameSuperclass && child.widget == newWidget) {
  newChild = child;

e.g 類似這種情況 返回的都是同一個實例化后的widget
典型的就是`const`優(yōu)化,因為`const`限制了同一個`widget`實例,則這個`widget`里面的`build`方法就不會被調(diào)用
class _Flutter_Test_Widget_LifecycleState
    extends State<Flutter_Test_Widget_Lifecycle> {
  bool a = true;
  HeHe he = HeHe("HEHE");
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          a = !a;
        });
      },
      child: a == true
          ? he
          : he,
//或者是 const  HeHe("HEHE");
    );
  }
}

//????符合這種情況則是child.widget和newWidget是同一類型,但是不是同一個widget實例
//則直接進行child.update操作,直接進行子節(jié)點的子節(jié)點的更新操作(A child B,B child C, 當前是A 判斷到B是同類型但是不同實例,
//則B進行update操作去觸發(fā)B的rebuild操作, A則不會執(zhí)行inflateWidget來對B進行操作,而是對B的子節(jié)點進行操作-性能優(yōu)化)
//??????大坑:child.widget和newWidget是同一類型,但是不是同一個widget實例
//??????會自動進行優(yōu)化,實際上掛載的child還是之前的child(child是elemet,也就是實際渲染元素)
//??????因為inflateWidget也不會調(diào)用,所以`initState`方法也不會被調(diào)用
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
          child.update(newWidget);

//??????widget既不符合同一個實例widget也不符合同一類型的weidget,則移除老節(jié)點,觸發(fā)inflateWidget,重新構(gòu)建子節(jié)點
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);

//????????如果child為空 即老節(jié)點為空子節(jié)點不為空
newChild = inflateWidget(newWidget, newSlot);

  //文字表述偽代碼 表示各種情況
  如果之前的位置child為null
  A、如果newWidget為null的話,說明這個位置始終沒有子節(jié)點,直接返回null即可。

  B、如果newWidget不為null,說明這個位置新增加了子節(jié)點調(diào)用inflateWidget(newWidget, newSlot)生成一個新的Element返回

  如果之前的child不為null
  C、如果newWidget為null的話,說明這個位置需要移除以前的節(jié)點,調(diào)用 deactivateChild(child)移除并且返回null
  D、如果newWidget不為null的話,先調(diào)用Widget.canUpdate(child.widget, newWidget)對比是否能更新。
????????這個方法會對比兩個Widget的runtimeType和key,如果一致則說明子Widget沒有改變,
????????只是需要根據(jù)newWidget(配置清單)更新下當前節(jié)點的數(shù)據(jù)child.update(newWidget);
  如果不一致說明這個位置發(fā)生變化,則deactivateChild(child)后返回inflateWidget(newWidget, newSlot)
}

//根據(jù)newWidget(配置清單)更新下當前節(jié)點的數(shù)據(jù)的update 會觸發(fā)`rebuild();`
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
//這個rebuild則是會再次觸發(fā)`performRebuild()`,進而`updateChild()`,于是子節(jié)點再走這樣的一個流程,子節(jié)點的子節(jié)點再走這樣的一個流程,不斷的遞歸直到頁面的最子一級節(jié)點
  rebuild();
}


開始FrameWork層會通知Engine表示自己可以進行渲染了,在下一個Vsync信號到來之時,Engine層會通過Windows.onDrawFrame回調(diào)Framework進行整個頁面的構(gòu)建與繪制。每次收到渲染頁面的通知后,Engine調(diào)用Windows.onDrawFrame最終交給_handleDrawFrame()方法進行處理。最后會走到WidgetsBinding.drawFrame()=>buildOwner.buildScope(renderViewElement)=>_dirtyElements[index].rebuild()=>performRebuild()這里會觸發(fā)當前element的widget的build方法=>updateChild()注意這里已經(jīng)是子節(jié)點進行接下來的操作了=>子節(jié)點update()=>子節(jié)點rebuild()=>子節(jié)點performRebuild()...

小結(jié):所以說在widget樹中,越高層的build()里調(diào)用setState()會導致遍歷所有的子節(jié)點=>遍歷所有子節(jié)點的子節(jié)點...

話術(shù)總結(jié):setState()會將當前的element標記為,并交由buildOwner,由buildOwner加入自己的臟列表中,等收到頁面渲染的通知后(這里流程簡略掉),會調(diào)用buildOwenr. buildScope (),這里會遍歷臟列表然后每一個都會調(diào)用rebuild(),rebuild()又會調(diào)用performRebuild(),performRebuild()則會調(diào)用build()方法重建當前的element,然后調(diào)用updateChild ()開始更新子節(jié)點,進而觸發(fā)子節(jié)點的rebuild()方法,進行下一輪的周期...一直到最后一個節(jié)點

ps:setState()只會觸發(fā)節(jié)點的build,并不會重復觸發(fā)initState...,所以不要再initState...里面進行一些傳值操作,這樣值是不會更新的(熱重載實際上是調(diào)用reassemble->markNeedsBuild,這也解釋了為什么initState不會在熱重載后重新調(diào)用)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容