Flutter: 源码解读Element机制

859 阅读11分钟

Element

在前面的描述中可以知道Widget和Element是属于响应式层面的概念,而RenderObject是属于渲染层面的概念.Widget是配置对象,RenderObject是被渲染引擎渲染的对象,Element是Widget和RenderObject之间的桥梁.

先说结论,在Flutter中一共存在两棵树,有几个Owner就有几棵树.有PipelineOwner和BuildOwner这两个Owner,就有RenderTree和ElementTree这两棵树,Widget只是这两棵树使用的配置对象.要理解这三个概念的关系需要从Element开始,因为Element是渲染逻辑的入口,Rendering Pipeline就是通过遍历dirty elements来完成的.

定义

官方对Element的解释是An instantiation of a [Widget] at a particular location in the tree,按字面意思是说它是树上的某个widget的实例,这种描述其实并不准确,因为Element毕竟不是Widget类的实例,之所以这么说是因为Element是用Widget中定义的createElement创建的.一种类型的widget对应一个相应的element.

abstract class Element implements BuildContext {
  Element _parent;

  Widget get widget => _widget;
  Widget _widget;

  BuildOwner get owner => _owner;
  BuildOwner _owner;

  RenderObject get renderObject 

  Element updateChild(Element child, Widget newWidget, dynamic newSlot)

  void mount(Element parent, dynamic newSlot)

  void markNeedsBuild()

  void rebuild()

  void unmount()
}

参考Element定义的源码可以注意到几个重要的属性和方法

  • _parent

    • 有_parent就说明了Element确实是一棵树,代表了父节点的引用
  • widget

    • 每个Element都会持有它的配置对象Widget
  • owner

    • 每个Element都会持有一个BuildOwner,这个是在WidgetBinding中初始化的
  • renderObject

    • 所有的Element都会有renderObject属性,但只有element的类型为RenderObjectElement时,其值才不会为null
  • updateChild

    • 新widget更新子element的方法,更新规则如下

    state_cycle

    根据原来child的有无和传入新widget的有无,共分了四种情况,这个方法会在rebuild中被调用

  • mount

    • 新创建的element第一次被挂载到父节点的时候会被调用
  • markNeedsBuild

    • 标记element为脏数据,并把它放进BuildOwner维护的一个全局的脏列表_dirtyElements里,调用前面owner属性的scheduleBuildFor方法就可以完成
  • rebuild

    • 第一次加载和widget改变都会调用这个方法重新构建element.在一次RenderPipeline中由BuildOwner遍历脏列表时在buildScope方法中发起调用
  • unmount

    • 卸载element

通过以上属性和方法可以感知到Element好像是有一个生命周期的,事实上这个生命周期确实是存在的,但这个生命周期一般只会被Framework使用到

  enum _ElementLifecycle {
    initial, //初始化
    active,  //活跃态,在屏幕上可见
    inactive,//非活跃态,在屏幕上不可见
    defunct, //废止态,再也不见
  }

Element类是个抽象类,是不能直接被使用的,所以在Flutter中定义了两种继承自Element的抽象子类ComponentElement和RenderObjectElement.

ComponentElement

这种类型的Element是一个起到包装作用的父Element容器,主要通过封装一些逻辑来管理其子Element.

  abstract class ComponentElement extends Element {
    Element _child;
    Widget build();
  }

通过源码可以发现,ComponentElement主要多了一个_child属性和一个build方法

  • _child

    这个属性表明在ComponentElement下面存在一个Element类型的子节点,这个子节点就是通过build返回的widget创建的.

    具体的创建过程定义在了inflateWidget中:

      Element inflateWidget(Widget newWidget, dynamic newSlot) {
        final Element newChild = newWidget.createElement();
        newChild.mount(this, newSlot);
        return newChild;
    }
    

    由此也可以得出一个结论,Element树其实是一棵组件树,也就是ComponentElement树.

  • build

    这个build返回一个新widget,这个需要被所有子类重写.

ComponentElement也是一个抽象类,同样不能被直接使用,所以又定义了三种继承自ComponentElement的子类StatelessElement、StatefulElement、ProxyElement.

StatelessElement
  class StatelessElement extends ComponentElement {
    @override
    Widget build() => widget.build(this);
  }

StatelessElement是以StatelessWidget为配置对象的Element,它会通过Widget.createElement()方法被创建.

这个Element主要重写了build方法,这个build方法调用了对应widget的build方法,在调用的时候会把element本身当作参数传进方法中,所以在widget的build方法中看到的BuildContext形参就是这个element实例.

build会在element实例中的performRebuild方法中被调用,StatelessElement拿到build返回的widget后会对子节点进行创建、更新等操作.

  void performRebuild() {
    Widget built;
    built = build();
    _child = updateChild(_child, built, slot);
  }  
StatefulElement
class StatefulElement extends ComponentElement {
    
      StatefulElement(StatefulWidget widget): _state = widget.createState(),super(widget) {
            _state._element = this;
            _state._widget = widget;
      }
    
      @override
      Widget build() => _state.build(this);
    
      State<StatefulWidget> get state => _state;
      State<StatefulWidget> _state;

      void _firstBuild() {
        final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic; 
        _state.didChangeDependencies();
        super._firstBuild();
      }
    
      @override
      void reassemble() {
        state.reassemble();
        super.reassemble();
      }
    
      @override
      void performRebuild() {
        if (_didChangeDependencies) {
          _state.didChangeDependencies();
          _didChangeDependencies = false;
        }
      super.performRebuild();
      }

      @override
      void update(StatefulWidget newWidget) {
        super.update(newWidget);
        final StatefulWidget oldWidget = _state._widget;
        _dirty = true;
        _state._widget = widget as StatefulWidget;
        try {
          _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
          final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
        } finally {
          _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
        }
        rebuild();
      }
    
      @override
      void activate() {
        super.activate();
        markNeedsBuild();
      }
    
      @override
      void deactivate() {
        _state.deactivate();
        super.deactivate();
      }
    
      @override
      void unmount() {
        super.unmount();
        _state.dispose();
        _state._element = null;
        _state = null;
      }
    
      @override
      InheritedWidget inheritFromElement(Element ancestor, { Object aspect }) {
        return dependOnInheritedElement(ancestor, aspect: aspect);
      }
    
      @override
      InheritedWidget dependOnInheritedElement(Element ancestor, { Object aspect }) {
        return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        _didChangeDependencies = true;
      }
}

StatefulElement是以StatefulWidget为配置对象的Element,它会通过Widget.createElement()方法被创建.

在源码中可以发现在StatefulElement内部重写了build方法,这个build方法调用的是_state.build而不是widget.build,这也是为什么StatefulWidget的build要在State中重写而不在StatefulWidget中重写.

同时在element的内部还调用了widget.createState、_state.initState()、_state.reassemble()、_state.didChangeDependencies()、_state.didUpdateWidget(oldWidget)、_state.deactivate()、_state.dispose()等方法,这些不正是state的几个生命周期嘛,在这里可以清楚的看到各个生命周期执行的时机.

  • widget.createState

    创建state实例的阶段,在调用widget.createElement对element进行初始化的时候会调用一次

  • _state.initState()

    初始化state阶段,这个方法在_firstBuild中被触发,也就是element还没开始挂载到树上.

  • _state.didChangeDependencies()

    state的依赖完成改变阶段,这个方法会在_firstBuild和performRebuild中触发:

    • 在_firstBuild中会紧随initState方法之后调用一次
    • 当重绘时在performRebuild方法中会有一次调用,会在didUpdateWidget调用之后发生
  • _state.reassemble()

    这是个会在开发阶段触发的一个热重载阶段,又Flutter的底层foundation层通过BuildOwner触发,这个阶段会发生在重新build之前

  • _state.didUpdateWidget(oldWidget)

    更新完widget后触发,这个函数会把old widget作为参数传进来.这个阶段的触发在_parent.build调用之后

  • _state.deactivate()

    state的非活跃阶段,这个方法会在_parent.updateChild中触发,但触发条件要分两种情况:

    • 移除一个element,也就是新widget为null,旧widget不为null,会调用deactivate方法,后续的didUpdateWidget阶段不会存在.
    • 改变一个element,也就是新widget与就widget不是同一个类型的widget或者key值不一样,也会调用deactivate方法
  • _state.dispose()

    • state的释放阶段,处于非活跃状态的element在一次rendering pipeline结尾如果仍然没有被重新放回到树中,则在Finalization中会卸载有所有的element,在卸载之前会触发state的dispose方法.

现在可以透过Element来总结一下State的生命周期

state_cycle

ProxyElement

ProxyElement以ProxyWidget为配置对象

abstract class ProxyElement extends ComponentElement {

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    super.update(newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}
  • build

    既然是个ComponentElement,重写build那就是必须的.但是这里重写的方式和前两个不一样,并不是调用widget的build方法,而是返回widget的child属性.也就是在编写ProxyWidget的时候不需要重写build函数而是为其child属性赋值就可以了,采用赋值的方式返回widget也就暗示了这个child属性最好不要改变.

  • update

    ProxyElement还重写了update函数,这个是很值得玩味的.可以先看看重写之前的update函数做了些什么

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

    可以看到原来的update就做了一件事情,用新的的widget给_widget属性赋值.而这里多做了两件事,额外调用了updated和rebuild方法.

    在前面分析中可以得知一个element在updateChild阶段会有四种情况,只有当新旧weidget的类型和key值相同的情况下才会走update这条线路.

      //in Element >> updateChild
      if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          child.update(newWidget);
          newChild = child;
      }
    

    而这里重写update的方式也就暗示了在ProxyElement中执行updateChild时,一定要走update(widget.canUpdate为true)这条线路,否则就没什么意义了,和前两种ELement就没啥区别.要保证在updateChild时执行update就需要新旧widget的runtimeType和key值不变,也就是它的widget.child属性的类型和key不要改变.

    所以ProxyElement适合放在最顶层,它的widget.child最好放一些Scaffold这样不会被改变类型的脚手架.

  • updated

    单独出这么一个函数来调用notifyClients是为了方便子类可以覆写它,可以加个判断用来决定是否要调用notifyClients.

  • notifyClients

    这是一个需要子类覆写的方法,通过名字可以看出它是一个实现了类似于消息通知的方法.

    从这个方法可以看出ProxyElement存在的意义和为什么叫ProxyElement了,这个Element就是一个代理、一个不赚差价的中间商,它本身并不生产widget,它只是child属性搬运工.它的子widget并不是使用它的widget.build方法创造出来的,而是在被实例化的时候传入的child属性,所有的子节点都可以订阅或修改消息,它负责分发消息给对应的子节点.

因为ProxyElement是个抽象类,是不能被实例化的,所以会使用到它的两个子类:

  • ParentDataElement

    和渲染对象有关,一般不会直接使用到,暂略.

  • InheritedElement

    这个Element可以说是非常有用,可以用它来做全局的状态管理

    现在可以从mount开始分析一下订阅的消息机制是如何工作的

        void mount(Element parent, dynamic newSlot) {
            _updateInheritance();
        }
    

    可以看到在mount中调用了_updateInheritance方法,而这个_updateInheritance方法可以在子类InheritedElement中找到

      Map<Type, InheritedElement> _inheritedWidgets;
      void _updateInheritance() {
              final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
              if (incomingWidgets != null)
                _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
              else
                _inheritedWidgets = HashMap<Type, InheritedElement>();
              _inheritedWidgets[widget.runtimeType] = this;
      }
    

    这里做了一件事情就是为_inheritedWidgets这个Map变量赋值,如果在父节点中存在_inheritedWidgets则会合并到这里,如果父节点没有则为_inheritedWidgets赋初始值,键值为widget的runtimeType,value为这个element.

    经过mount一波猛如虎的操作这个InheritedElement就会被挂载到Element Tree中,此时并没有发生什么,接下来就是子节点要在这个代理中注册想要对消息.这个注册方法需要在InheritedElement的widget中手动定义,官方简易的注册写法是这样的

       static InheritedWidget_Name of(BuildContext context) {
          return context.dependOnInheritedWidgetOfExactType<InheritedWidget_Name>();
       }
    

    这个of方法要在子Widget的build中调用来获取InheritedWidget_Name实例,可以通过这个实例拿到存储在InheritedWidget_Name中的状态供子widget使用,但在这个获取实例的过程中会悄悄注册消息订阅事件.

        @override
        T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
          final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
          if (ancestor != null) {
            return dependOnInheritedElement(ancestor, aspect: aspect) as T;
          }
          _hadUnsatisfiedDependencies = true;
          return null;
        }
        Set<InheritedElement> _dependencies;
        @override
        InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
          _dependencies ??= HashSet<InheritedElement>();
          _dependencies.add(ancestor);
          ancestor.updateDependencies(this, aspect);
          return ancestor.widget;
        }
    

    在dependOnInheritedWidgetOfExactType中可以看到子节点拿到了它的代理ancestor并调用了dependOnInheritedElement,这个dependOnInheritedElement调用了代理element的updateDependencies方法并把子element作为参数传入,最后返回代理的widget(这里面存储着子widget需要的data).

    
      @protected
      void updateDependencies(Element dependent, Object aspect) {
        setDependencies(dependent, null);
      }
    
      final Map<Element, Object> _dependents = HashMap<Element, Object>();
    
      void setDependencies(Element dependent, Object value) {
        _dependents[dependent] = value;
      }
    
    

    又可以看到在updateDependencies中调用了setDependencies,这个setDependencies就是为代理中的_dependents赋值,_dependents的key值是子element实例.

    经过以上步骤就把子Element的实例注册在了InheritedElement中的_dependents属性的keys中.当触发渲染流水线重绘走到element的update函数时,就会调用前面说的在ProxyElement中定义的updated方法,这个方法会被InheritedElement重写

      @override
      void updated(InheritedWidget oldWidget) {
          if (widget.updateShouldNotify(oldWidget))
              super.updated(oldWidget);
      }
    

    重写的目的就是加了一层判断,需要开发者根据新旧widget自行判断是否要通知子节点触发didChangeDependencies事件,这个判断是在InheritedWidget中被重写的.如果返回true则会触发notifyClients通知注册的子element调用didChangeDependencies

       @override
       void notifyClients(InheritedWidget oldWidget) {
          for (final Element dependent in _dependents.keys) {
            notifyDependent(oldWidget, dependent);
          }
       }
    
      @protected
      void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
          dependent.didChangeDependencies();
      }
    

以上就是ComponentElement的三种类型,ComponentElement存在的意义就是创建子节点,如果Flutter只使用ComponentElement那么在屏幕中是什么也不会看到的,会在内存中建立一个无穷尽的Element树,直到内存溢出或报错,因为每个ComponentElement一定会有build方法返回一个不能为空的子节点widget.所以这个ComponentElement树的每个分支一定要有个尽头,这个尽头就是RenderObjectElement.

RenderObjectElement

RenderObjectElement是比较复杂的,首先它也具有ComponentElement的功能也是可以产生树结构的,只是它的子节点不但可以是child还可以是children,而ComonentElement只有child.其次,RenderObjectElement是一个有布局、有样式、有画面的Element,因为每个RenderObjectElement都有一个属性来持有RenderObject,这个RenderObject就是最终要被渲染的东西,而且由于ComponentElement的存在,会导致Element树和Render树上的节点并不是一一对应的.所以RenderObjectElement中存在渲染逻辑不但要完成树的构建还要把RenderObject的变化同步到Render Tree中.

TO BE CONTINUED...