美文网首页
二、Flutter的渲染机制以及setState()背后的原理

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

作者: 帅气的阿斌 | 来源:发表于2022-01-21 14:47 被阅读0次

    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
    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,
        );
      }
    }
    
    //❗️❗️符合这种情况则是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不会在热重载后重新调用)

    相关文章

      网友评论

          本文标题:二、Flutter的渲染机制以及setState()背后的原理

          本文链接:https://www.haomeiwen.com/subject/yduevltx.html