美文网首页
Flutter更新机制分析

Flutter更新机制分析

作者: 夜色流冰 | 来源:发表于2019-03-25 16:07 被阅读0次

    本篇博客将要分析Widget的更新机制,在阅读这篇文章之前建议读者阅读Fultter之Element和Widget对应关系解析, 从Element和Widget对应关系这篇博文中可以知道有如下的表关系:

    Widget 说明 举例
    MultiChildRenderObjectWidget 该类型的Widget可以添加多个widget Row,Column,Stack
    SingleChildRenderObjectWidget 该类型的widget只能添加一个widget Center,Padding,Container
    LeafRenderObjectWidget 该类型是树的叶子节点,故不能添加widget Text,Image,Semantics

    我们知道Widget的解析在mount里面进行的,mout调用了Element的inflateWidget方法开始解析布局:这个观点可以在MultiChildRenderObjectWidget的mount代码得到验证,代码如下:

    void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        _children = List<Element>(widget.children.length);
        Element previousChild;
        //循环调用各个widget
        for (int i = 0; i < _children.length; i += 1) {
          //调用element的inflateWidget对widget.children[i]
          final Element newChild = inflateWidget(widget.children[i], previousChild);
          _children[i] = newChild;
          previousChild = newChild;
        }
      }
    

    当然本文的主题是Flutter的更新机制,为了方便说明本文以SingleChildRenderObjectWidget为参考 来说明所以详细看下SingleChildRenderObjectWidget的源码:

     abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
    
     //有且仅有一个child
      final Widget child;
      
      //创建一个SingleChildRenderObjectElement并将this即传入进去
      @override
      SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
    }
    

    正如代码所示SingleChildRenderObjectWidget 是一个只包含一个Child Widget的组件。其Element对应的是SingleChildRenderObjectElement,主要代码如下所示:

    
    class SingleChildRenderObjectElement extends RenderObjectElement {
      
      SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
    
    //对应子Widget的Element对象。
      Element _child;
     
      @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        //创建子child Widget对应的Element对象,第三个参数传null
        _child = updateChild(_child, widget.child, null);
      }
     
    }
    
    
    

    观察SingleChildRenderObjectWidget和SingleChildRenderObjectElement 可以发现二者都包含了对应的child对象,比如SingleChildRenderObjectWidget拥有了一个child Widget对象。而SingleChildRenderObjectElement 拥有了一个 _child Element对象。SingleChildRenderObjectElement 的mount方法调用了updateChild方法,顾名思义就是更新child。

    其实也是做了两件事:
    1.如果符合flutter的更新条件,则进行更新操作
    2.如果不符合更新,则调用inflateWidget进行UI的重新构建。


    让我们来详细看看updateChild方法,该方法在Element对象里。

     Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
      
        //如果newWidget是null,且child不是null,那么我们需要从render tree中删除这个widget。
        //同时返回一个null
        if (newWidget == null) {
          if (child != null)
            deactivateChild(child);
          return null;
        }
        
        if (child != null) {
          if (child.widget == newWidget) {
            //省略处理slot的逻辑
            //返回旧的element
            return child;
          }
          //如果满足更新机制
          if (Widget.canUpdate(child.widget, newWidget)) {
            //省略处理slot的逻辑
            //则更新
            child.update(newWidget);
            //返回一个新的
            return child;
          }
          deactivateChild(child);
        }
        //不符合更新逻辑,则返回一个新的element对象
        return inflateWidget(newWidget, newSlot);
      }
       
    

    下面先来说明下updateChild的前参数:child代表的是old Widget所对应的Element对象。newWidget代表的是最新的widget对象,至于第三个 newSlot可能是一个Element(详见MultiChildRenderObjectElement的mount方法),需要注意的是对于只有一个child的Widget组件,Flutter建议slot传null Subclasses of Element that only have one child should use null for the slot for that child.,正如SingleChildRenderObjectElement 这样,调用updateChild方法第三个参数直接是null。那么Element的更新规则是什么呢?先总结贴一下官网对该方法返回值的总结:

    newWidget == null newWidget != null
    child == null Returns null. Returns new [Element].
    child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].

    可以看出有两种情况:newWidget等于null和newWidget !=null

    1、newWidget等于null的情况:updateChild方法返回的是一个null,且如果child这个老的element 不为null则删除这个child。
    2、newWidget !=null的情况:如果child==null,则返回newWidget 所关联的Element(通过newWidget.createElement方法创建),如果child!=null,则如果满足复用条件,则返回原来的child,否则还是返回新的Element对象,即newWidget创建的Element对象。

    child这个element满足复用的条件有两个,第一个是child.widget == newWidget

      if (child.widget == newWidget) {
            //返回旧的element
            return child;
          }
    

    显而易见,如果child原来的widget和newWidget相等,肯定直接复用child这个Element及其widget对象。
    第二个条件就是Widget的静态方法canUpdate返回true:

      static bool canUpdate(Widget oldWidget, Widget newWidget) {
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
    

    方法也很简单:当且仅当新旧两个widget的runtimeType相等且两个key也相等的时候就采取更新措施:

       if (Widget.canUpdate(child.widget, newWidget)) {
            //省略处理slot的逻辑
            //则更新
            child.update(newWidget);
            //返回一个新的
            return child;
          }
    

    另外child.update方法也很简单,就是将新的widget对象赋值给child的widget引用,代码如下:

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

    到此算是分析完了Widget的更新机制,通过代码来看与其说是更新了widget,不如说是更新了Element。到此为止,本篇博文就此结束,如有不当之处欢迎批评指正,另外以两个问题来结束本篇博文:
    1.widget 的 runtimeType是什么?
    2.widget的key的具体作用是什么?
    后面会继续分析

    相关文章

      网友评论

          本文标题:Flutter更新机制分析

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