Flutter源码分析(一)先从StatelessWidget开始

1,415 阅读7分钟

         我们在写Flutter第一行代码的时候应该就接触过这StatelessWidget StatefulWidget两个抽象类,而我们在用Flutter开发应用的时候也会经常和这两个类打交道。 那么这两个类到底是用来干什么的呢?让我们来通过源码分析一下。

StatelessWidget

        StatelessWidget是无状态的Widget的抽象类,所有继承它的都是无状态的Widget。那么无状态是什么意思呢?意思是不需要管理内部状态。额外的补充一下:我们所看到的Flutter的UI界面是当前一帧显示出来的,当我们更新UI的时候会为我们显示下一帧的UI式样。所以Flutter显示的style是通过每一帧来显示的。简单可以理解为从上一帧更新到下一帧StatelessWidget是不能修改和保存状态的。比如通过按钮修改某个Widget的颜色,进行刷新到下一帧显示出来,这个是无法用StatelessWidget去实现的,它只能展示无状态的Widget。那么有状态的Widget如何实现呢?是通过StatefulWidget来实现的,下一篇文章会详细讲解。我们来看一下StatelessWidget的源码:

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

        很简单的一个抽象类,继承它的子类只需要实现Widget build(BuildContext context) 这个方法即可。在StatelessWidget这个抽象类里边看到了已经为我们创建好了一个StatelessElement(this)的Element,会将当前的Widget对象传入进去。那么StatelessElement是用来干什么的呢?

         Flutter中Widget是以树的结构进行渲染的,而实际上Element才是树的节点,所有对Widget进行添加,删除,修改等等操作实际上是对Element进行操作的,而Widget其实是Element的配置项而已。

StatelessElement

        先来看一下StatelessElement的继承关系:Element -> ComponentElement -> StatelessElement, StatefulElement

        StatefulElement同样也是StatefulWidget创建出来的Element。在截图上我们可以看到有这样一个RenderObjectElement的Element,这里简单说一下Flutter中还有一个特别的对象就是RenderObject,它是用来进行组件的paint,layout的。总结一下:ComponentElement是用来对子组件排版的,RenderObjectElement是用来进行布局和绘制的。有关RenderObject的源码分析会在接下里的文章进行讲解。先来看一下StatelessElement的源码:

/// An [Element] that uses a [StatelessWidget] as its configuration.
class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

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

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}

        也是一个很简单的一个类,总共实现了如下几个步骤:

  • 持有了Widget对象的引用。
  • 实现build的方法去调用widget的build方法。
  • 重写update的方法,添加标记肮脏"_dirty"的字段,之后调用rebuild的方法。

StatelessWidget执行流程

         我们来看一下StatelessWidget的执行流程:首先Flutter的根节点默认会执行inflateWidget(Widget newWidget, dynamic newSlot),需要传入的参数是一个widget和一个插槽slot。

abstract class Element extends DiagnosticableTree implements BuildContext {
....
  @protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    assert(newWidget != null);
    final Key key = newWidget.key;
    if (key is GlobalKey) {
      final Element newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        assert(newChild._parent == null);
        assert(() {
          _debugCheckForCycles(newChild);
          return true;
        }());
        newChild._activateWithParent(this, newSlot);
        final Element updatedChild = updateChild(newChild, newWidget, newSlot);
        assert(newChild == updatedChild);
        return updatedChild;
      }
    }
    final Element newChild = newWidget.createElement();
    assert(() {
      _debugCheckForCycles(newChild);
      return true;
    }());
    newChild.mount(this, newSlot);
    assert(newChild._debugLifecycleState == _ElementLifecycle.active);
    return newChild;
  }
....
}

代码中首先先判断widget的当前的key是否为GlobalKey。如果对widget的key设置为GlobalKey,它会通过当前的key进行静态的GlobalKey的Map里边去查找对应的Element,如果找到了会调用updateChild(newChild, newWidget, newSlot);这个方法更新Element并得到更新好的节点Element,没有的话进行接下来的createElement的过程。至于如何将key和对应的Element存入到Map的时机是在mount这个方法中进行的,后文会讲述。

  Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
    final Element element = key._currentElement;
    if (element == null)
      return null;
    if (!Widget.canUpdate(element.widget, newWidget))
      return null;
    assert(() {
      if (debugPrintGlobalKeyedWidgetLifecycle)
        debugPrint('Attempting to take $element from ${element._parent ?? "inactive elements list"} to put in $this.');
      return true;
    }());
    final Element parent = element._parent;
    if (parent != null) {
      assert(() {
        if (parent == this) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary("A GlobalKey was used multiple times inside one widget's child list."),
            DiagnosticsProperty<GlobalKey>('The offending GlobalKey was', key),
            parent.describeElement('The parent of the widgets with that key was'),
            element.describeElement('The first child to get instantiated with that key became'),
            DiagnosticsProperty<Widget>('The second child that was to be instantiated with that key was', widget, style: DiagnosticsTreeStyle.errorProperty),
            ErrorDescription('A GlobalKey can only be specified on one widget at a time in the widget tree.'),
          ]);
        }
        parent.owner._debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans(
          parent,
          key,
        );
        return true;
      }());
      parent.forgetChild(element);
      parent.deactivateChild(element);
    }
    assert(element._parent == null);
    owner._inactiveElements.remove(element);
    return element;
  }
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {

  factory GlobalKey({ String debugLabel }) => LabeledGlobalKey<T>(debugLabel);


  const GlobalKey.constructor() : super.empty();

  static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
  static final Set<Element> _debugIllFatedElements = HashSet<Element>();

 Element get _currentElement => _registry[this];

         之后会执行创建新的子节点Element,调用当前节点的mount挂载方法将当前的父节点Element和插槽Slot传入进去。

final Element newChild = newWidget.createElement();

newChild.mount(this, newSlot);

         来看一下mount方法到底干了些什么呢?源码如下:

  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    assert(_debugLifecycleState == _ElementLifecycle.initial);
    assert(widget != null);
    assert(_parent == null);
    assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
    assert(slot == null);
    assert(depth == null);
    assert(!_active);
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
    assert(() {
      _debugLifecycleState = _ElementLifecycle.active;
      return true;
    }());
  }

         其实就是将父节点和插槽保存下来,看到了如果key是GlobalKey的话将当前的Element对象注册到GlobalKey的Map中保存。

        _updateInheritance(); 这个方法是为了将所有父节点的InheritedElement以Map的形式保存在当前子节点上,暴露ancestorInheritedElementForWidgetOfExactType, getElementForInheritedWidgetOfExactType这两个方法可以获取到对应父节点的InheritedElement。如果对InheritedElement不熟悉的同学可以先了解一下InheritedWidget
        Element继承关系:Element -> ComponentElement -> ProxyElement -> InheritedElement
        会在接下来文章里边详细的说ProxyWidget,这里就不在提及了。

abstract class Element
...
  @override
  void _updateInheritance() {
    assert(_active);
    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;
  }
  
  void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
  
  Map<Type, InheritedElement> _inheritedWidgets;
  
    @override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }
  
    @override
  InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    return ancestor;
  }

  @override
·    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
  }
...
} 

         顶级父类Element的mount方法看完了,我们来看一下它的子类ComponentElement源码:

abstract class ComponentElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  ComponentElement(Widget widget) : super(widget);

  Element _child;

  bool _debugDoingBuild = false;
  @override
  bool get debugDoingBuild => _debugDoingBuild;

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
    _firstBuild();
    assert(_child != null);
  }

  void _firstBuild() {
    rebuild();
  }

  /// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
  /// (for stateless widgets) or the [State.build] method of the [State] object
  /// (for stateful widgets) and then updates the widget tree.
  ///
  /// Called automatically during [mount] to generate the first build, and by
  /// [rebuild] when the element needs updating.
  @override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      built = build();
      assert(() {
        _debugDoingBuild = false;
        return true;
      }());
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

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

  @override
  void visitChildren(ElementVisitor visitor) {
    if (_child != null)
      visitor(_child);
  }

  @override
  void forgetChild(Element child) {
    assert(child == _child);
    _child = null;
    super.forgetChild(child);
  }
}

         它重写了父类Element的mount的方法,在调用完父类的mount方法又调用了_firstBuild();这个方法,这个方法里边就一行代码就是调用了父类Element的rebuild方法。

  void _firstBuild() {
    rebuild();
  }
abstract class Element extends DiagnosticableTree implements BuildContext {
...
  /// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
  /// called to mark this element dirty, by [mount] when the element is first
  /// built, and by [update] when the widget has changed.
  void rebuild() {
    assert(_debugLifecycleState != _ElementLifecycle.initial);
    if (!_active || !_dirty)
      return;
    assert(() {
      if (debugOnRebuildDirtyWidget != null) {
        debugOnRebuildDirtyWidget(this, _debugBuiltOnce);
      }
      if (debugPrintRebuildDirtyWidgets) {
        if (!_debugBuiltOnce) {
          debugPrint('Building $this');
          _debugBuiltOnce = true;
        } else {
          debugPrint('Rebuilding $this');
        }
      }
      return true;
    }());
    assert(_debugLifecycleState == _ElementLifecycle.active);
    assert(owner._debugStateLocked);
    Element debugPreviousBuildTarget;
    assert(() {
      debugPreviousBuildTarget = owner._debugCurrentBuildTarget;
      owner._debugCurrentBuildTarget = this;
      return true;
    }());
    performRebuild();
    assert(() {
      assert(owner._debugCurrentBuildTarget == this);
      owner._debugCurrentBuildTarget = debugPreviousBuildTarget;
      return true;
    }());
    assert(!_dirty);
  }
  ...
  }

         rebuild方法里边它首先回去判断if (!_active || !_dirty)是不是肮脏标记,还记得StatelessElement的update方法中会将_dirty设置为true,而_active用来表示该节点的生命状态,在mount,activate方法中是设置为true,在deactivate设置为false。所以要想执行performRebuild方法的话需要满足两个条件。1、标记肮脏。2、该节点还挂载上边并在当前视图上。那么继续向下分析performRebuild方法。先看一下它的源码:

abstract class ComponentElement extends Element {
...
 /// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
  /// (for stateless widgets) or the [State.build] method of the [State] object
  /// (for stateful widgets) and then updates the widget tree.
  ///
  /// Called automatically during [mount] to generate the first build, and by
  /// [rebuild] when the element needs updating.
  @override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      built = build();
      assert(() {
        _debugDoingBuild = false;
        return true;
      }());
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }
  ...
  }

         performRebuild这个方法在顶级父类Element是一个抽象方法,这个方法的作用就是实现子类的布局,所以需要让子类ComponentElement去实现相应的逻辑。这个方法主要做了三件事:

  • 执行子类重写的build的方法获取到Widget。
  • 肮脏的标记改成false。
  • 调用updateChild这个方法,更新Element。

         关于updateChild这个方法很重要,我们从中可以发现Flutter对Element的优化。那么先来看一下它的源码:

  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      // When the type of a widget is changed between Stateful and Stateless via
      // hot reload, the element tree will end up in a partially invalid state.
      // That is, if the widget was a StatefulWidget and is now a StatelessWidget,
      // then the element tree currently contains a StatefulElement that is incorrectly
      // referencing a StatelessWidget (and likewise with StatelessElement).
      //
      // To avoid crashing due to type errors, we need to gently guide the invalid
      // element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
      // returns false which prevents us from trying to update the existing element
      // incorrectly.
      //
      // For the case where the widget becomes Stateful, we also need to avoid
      // accessing `StatelessElement.widget` as the cast on the getter will
      // cause a type error to be thrown. Here we avoid that by short-circuiting
      // the `Widget.canUpdate` check once `hasSameSuperclass` is false.
      assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }

    assert(() {
      if (child != null)
        _debugRemoveGlobalKeyReservation(child);
      final Key key = newWidget?.key;
      if (key is GlobalKey) {
        key._debugReserveFor(this, newChild);
      }
      return true;
    }());

    return newChild;
  }

一、先判断当前child.widget和newWidget是不是同一个对象,如果是的话直接返回当前节点即可。
二、调用Widget.canUpdate(child.widget, newWidget)这个方法比较Widget是否需要更新,比较的内容就是oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;,同时满足的话调用child.update(newWidget);更新一下当前的widget既可。
三、以上都不满足的话就只能将它销毁掉重新执行inflateWidget创建Element了。

        当前更新child的逻辑会将更新时候性能的销毁降到最低,因为频发的创建或者销毁Element会带来一定的性能开销。

         到这为止就将StatelessWidget挂载的流程已经完成了。