美文网首页flutter
flutter绘制流程——rebuild

flutter绘制流程——rebuild

作者: 第八区 | 来源:发表于2022-01-10 10:42 被阅读0次

    rebuild是Element的方法,有两种场景下会被调用:

    1. element第一次构建mount的时候
    2. widget发生变化的时候
    void rebuild() {
      if (_lifecycleState != _ElementLifecycle.active || !_dirty)
        return;
      performRebuild();
    }
    

    主要逻辑分为2步

    1. 判断状态是否是active,_dirty是否为true
    2. 执行performRebuild(),这是个抽象方法,所以具体rebuild的逻辑由element子类去实现

    下面重点来看performRebuild()

    performRebuild.png

    1 performRebuild()

    顾名思义真正执行重新build的地方,因此每个实现类会有所不同,下面看下不同类型Element的实现

    1.1 RenderObjectElement

    更新renderObject,当然还有一些RenderObjectElement的继承类可能还做了其他逻辑

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

    1.1.1 SliverMultiBoxAdaptorElement

    对于SliverGrid,SliverList,ListView都会用到它,这里逻辑比较多,今天这篇不去细讲,大概了解有关键逻辑 _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. 执行build(),作为新的newWidget
    2. _child = updateChild(_child, built, slot);

    1.2.1 StatelessElement

    未覆写,逻辑同ComponentElement的performRebuild()

    1.2.2 StatefulElement

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

    在build之前判断需要didChangeDependencies

    总结:performRebuild()实现分两类,ComponentElement和RenderObjectElement

    • RenderObjectElement会updateRenderObject,对于有child的继承类会进行_build(index)和updateChild
    • ComponentElement的performRebuild主要分为两步。1:build(); 2:updateChild。下面依次展开

    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();
    

    用来build返回widget,这个我们就很熟悉了,写UI代码主要围绕在这一块

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

    2.2 SliverMultiBoxAdaptorElement

    在1.1.1中的performRebuild()执行的是_build

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

    看到SliverChildDelegate中的定义,和ComponentElement的build意思差不多,只不是事根据传入index来返回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);
    

    总结:不管是ComponentElement中的build,还是SliverMultiBoxAdaptorElement中的_build,最终都是用来构建一个Widget。

    下面看到performRebuild的下一步updateChild。

    3 updateChild

    3.1 Element/ComponentElement

    Element? updateChild(Element? child, Widget? newWidget, Object? newSlot),用来更新子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)) {
          //可以update的情况,也就是runtimetype和key相同
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          child.update(newWidget);
          newChild = child;
        } else {
          //其他情况,移除旧的,重新inflateWidget新的widget,会创建element
          deactivateChild(child);
          newChild = inflateWidget(newWidget, newSlot);
        }
      } else {
        //old child是null,这里直接inflate新的widget,会创建element
        newChild = inflateWidget(newWidget, newSlot);
      }
      return newChild;
    }
    

    总共分以下几种情况

    1. newWidget是null,则清理掉old child(如果old child不为null),返回null
    2. 新旧widget相同,说明数据没有变化,直接返回旧的child
    3. widget可以update,child.update(newWidget); 直接更新old child即可
    4. 其他情况,重新inflateWidget,这里会createElement,清理掉old child(如果old child不为null)

    3.2 SliverMultiBoxAdaptorElement

    RenderObjectElement的实现类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;
    }
    

    下面看几个典型的实现类

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

    总结:对于update方法首先一定会做的就是更新_widget。然后对于ComponentElement和RenderObjectElement的逻辑有所不同。

    • ComponentElement主要会进行rebuild();这样又回到最初的rebuild,只是到了子节点
    • RenderObjectElement则会更新自己的renderObject,然后根据拥有child是否是多个逻辑有所不同,如:
      • SingleChildRenderObjectElement只有一个child,执行updateChild 这样也回到了前面的步骤3
      • MutliChildRenderObjectElement可能有多个child,执行updateChildren

    下面重点开看updateChildren

    5 updateChildren

    RenderObjectElement中定义,针对可能有多个child的element的更新逻辑

    5.1 定义新旧children的开始和结束位置,用于后面遍历

    //定义old和new的首尾位置
    int newChildrenTop = 0;
    int oldChildrenTop = 0;
    int newChildrenBottom = newWidgets.length - 1;
    int oldChildrenBottom = oldChildren.length - 1;
    //根据old和new的长度判断,如果相同则newChildren直接使用oldChildren,如果不同,则创建一个长度为newWidgets.length的list,使用_NullElement.instance来填充
    final List<Element> newChildren = oldChildren.length == newWidgets.length ?
    oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);
    

    5.2 从开始位置遍历children,主要处理可以直接updateChild的情况,碰到不能update则直接跳出,newChildrenTop和oldChildrenTop会指向不能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))!;
      //设置到newChild中
      newChildren[newChildrenTop] = newChild;
      //设置上一个child
      previousChild = newChild;
      //移动到下一个位置
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }
    

    5.3 从底部开始遍历判断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的区别是没有去设置previousChild了,并且没有updateChild
      oldChildrenBottom -= 1;
      newChildrenBottom -= 1;
    }
    

    5.4 从oldChildrenTop开始遍历oldChildren,取出widget.key不为null的child,存入oldKeyedChildren,后面可能取出进行复用,这里oldChildrenTop应该等于oldChildrenBottom+1

    // Scan the old children in the middle of the list.
    // 根据top和bottom位置判断是否还存在中间的元素没有处理
    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
    //用于存放有key的old child
    Map<Key, Element>? oldKeyedChildren;
    if (haveOldChildren) {
      oldKeyedChildren = <Key, Element>{};
      //从顶部开始遍历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
            //没有直接废弃oldChild
            deactivateChild(oldChild);
        }
        //注意:这里是old child的位置移动
        oldChildrenTop += 1;
      }
    }
    

    5.5 从newChildrenTop开始遍历newWidgets,根据key从oldKeyedChildren取出old child,然后判断是否可以直接update,如果可以则在updateChild的作为oldChild参数传入,否则传null。到这里newChildrenTop应该等于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的标记,并没有真正的updateChild,所以。下面重置newChildrenBottom和oldChildrenBottom。继续从oldChildrenTop开始遍历,然后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 清理没有复用成功的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,同时又没能复用的child进行clean
          if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
            deactivateChild(oldChild);
        }
      }
      return newChildren;
    }
    

    总结:这么长的流程和逻辑主要是为了对多个children的情况要进行判断是否可以复用,对于不能复用的child进行清理,最终针对child还是会执行到updateChild这样又回到了3

    6 inflateWidget

    Element中定义,在第3节中 updateChild如果old child是空或者无法update就需要inflateWidget

    写android的朋友应该很熟悉了,android里有LayoutInflater.from().inflate(),从xml来解析获取到View;同样在这里通过widget来解析返回Element。关于GlobalKey的逻辑,我们先忽略,后面再介绍。下面的逻辑就简单了,创建一个element,然后mount到当前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!;
        }
      }
      //创建子Element
      final Element newChild = newWidget.createElement();
      //将child挂载到当前element
      newChild.mount(this, newSlot);
      //返回child element
      return newChild;
    }
    

    7 总结

    走完上面整个rebuild流程,第一感受就是在于ComponentElement和RenderObjectElement在流程上有明显的区别,这也回到这两类Element的设计,RenderObjectElement不一定包含子child,但它包括renderObject用于渲染,而ComponentElement是一种组成的Element,它并不包含RenderObject,但它会有子 Element。因此在rebuild时ComponentElement只需要关心child的update,而RenderObjectElement还需要关注RenderObject的更新。另外在多child的情况如:第5节,diff的逻辑会稍微复杂一点。

    对于整个流程中的关于方法我们也要熟悉,如:update,inflateWidget,updateChild。

    相关文章

      网友评论

        本文标题:flutter绘制流程——rebuild

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