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

1.setState()干了啥?

@protected
void setState(VoidCallback fn) {         
  //调用我们传入的函数
  final dynamic result = fn() as dynamic;
  //当前的element执行markNeedsBuild方法
  _element.markNeedsBuild();
}

/// owner The object that manages the lifecycle of this element.
/// BuildOwner owner负责管理所有element的构建以及生命周期
void markNeedsBuild() {
  //element将自己标记为脏
  _dirty = true;
  //scheduleBuildFor 译:计划构建
  owner.scheduleBuildFor(this);
}

void scheduleBuildFor(Element element) {
  onBuildScheduled!();
  //将element添加到BuildOwner的_dirtyElements集合中
  _dirtyElements.add(element);
  //当前element已在脏列表中属性设置为true
  element._inDirtyList = true;
}

  • 小结: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树的根节点
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
        //将不再活跃的节点从UI树中移除
    buildOwner.finalizeTree();
  } finally {
        /·················/
  }
}

void buildScope(Element context, [ VoidCallback callback ]) {
...
//取出脏elements调用rebuild() rebuild方法最终会触发performRebuild
   final Element element = _dirtyElements[index];
   element.rebuild();
...
}

void performRebuild() {
  Widget built;
//这里解释了为啥setState会触发build方法
  built = build();

//执行updateChild操作(即更新得到最新的子节点)
//_child为当前element的子element,built则是build后element最新当前的widget
  _child = updateChild(_child, built, slot);
}

//setState会触发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) {
//新的子节点是空的 老的是有子节点 则清除子节点即可
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }

//❗️新的节点和老的是同一个Widget实例 则return newChild;
if (hasSameSuperclass && child.widget == newWidget) {
  newChild = child;

e.g 类似这种情况 返回的都是同一个实例化后的widget
典型的就是`const`优化,因为`const`限制了同一个`widget`实例,则这个`widget`里面的`build`方法就不会被调用
class _Flutter_Test_Widget_LifecycleState
    extends State {
  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操作,直接进行子节点的子节点的更新操作(A child B,B child C, 当前是A 判断到B是同类型但是不同实例,则B进行update操作去触发B的rebuild操作, A则不会执行inflateWidget来对B进行操作,而是对B的子节点进行操作-性能优化)
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
          child.update(newWidget);

//❗️❗️❗️widget既不符合同一个实例widget也不符合同一类型的weidget,则移除老节点,触发inflateWidget,重新构建子节点
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);

//❗️❗️❗️❗️如果child为空 即老节点为空子节点不为空
newChild = inflateWidget(newWidget, newSlot);

  //文字表述伪代码 表示各种情况
  如果之前的位置child为null
  A、如果newWidget为null的话,说明这个位置始终没有子节点,直接返回null即可。

  B、如果newWidget不为null,说明这个位置新增加了子节点调用inflateWidget(newWidget, newSlot)生成一个新的Element返回

  如果之前的child不为null
  C、如果newWidget为null的话,说明这个位置需要移除以前的节点,调用 deactivateChild(child)移除并且返回null
  D、如果newWidget不为null的话,先调用Widget.canUpdate(child.widget, newWidget)对比是否能更新。
❗️❗️❗️❗️这个方法会对比两个Widget的runtimeType和key,如果一致则说明子Widget没有改变,
❗️❗️❗️❗️只是需要根据newWidget(配置清单)更新下当前节点的数据child.update(newWidget);
  如果不一致说明这个位置发生变化,则deactivateChild(child)后返回inflateWidget(newWidget, newSlot)
}

//根据newWidget(配置清单)更新下当前节点的数据的update 会触发`rebuild();`
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
//这个rebuild则是会再次触发`performRebuild()`,进而`updateChild()`,于是子节点再走这样的一个流程,子节点的子节点再走这样的一个流程,不断的递归直到页面的最子一级节点
  rebuild();
}


开始FrameWork层会通知Engine表示自己可以进行渲染了,在下一个Vsync信号到来之时,Engine层会通过Windows.onDrawFrame回调Framework进行整个页面的构建与绘制。每次收到渲染页面的通知后,Engine调用Windows.onDrawFrame最终交给_handleDrawFrame()方法进行处理。最后会走到WidgetsBinding.drawFrame()=>buildOwner.buildScope(renderViewElement)=>_dirtyElements[index].rebuild()=>performRebuild()这里会触发当前element的widget的build方法=>updateChild()注意这里已经是子节点进行接下来的操作了=>子节点update()=>子节点rebuild()=>子节点performRebuild()...

小结:所以说在widget树中,越高层的build()里调用setState()会导致遍历所有的子节点=>遍历所有子节点的子节点...

话术总结:setState()会将当前的element标记为,并交由buildOwner,由buildOwner加入自己的脏列表中,等收到页面渲染的通知后(这里流程简略掉),会调用buildOwenr. buildScope (),这里会遍历脏列表然后每一个都会调用rebuild(),rebuild()又会调用performRebuild(),performRebuild()则会调用build()方法重建当前的element,然后调用updateChild ()开始更新子节点,进而触发子节点的rebuild()方法,进行下一轮的周期...一直到最后一个节点

ps:setState()只会触发节点的build,并不会重复触发initState...,所以不要再initState...里面进行一些传值操作,这样值是不会更新的(热重载实际上是调用reassemble->markNeedsBuild,这也解释了为什么initState不会在热重载后重新调用)

  • 参考资料
    原来我一直在错误的使用 setState()?
    Flutter - setState更新机制

你可能感兴趣的:(二、Flutter的渲染机制以及setState()背后的原理)