当前位置:   article > 正文

透过源码理解Flutter中widget、state和element的关系_flutter widget源码

flutter widget源码

1、framework源码组成

Flutter中widget、state、element的源码位于framework.dart中,整个文件6693行(版本Flutter 3.12.0-14.0.pre.28)。整个代码可划分为若干部分,主要包括key、widget、state、element四部分。

1.1 key

关于key的代码65行到272行,这里的key包括ObjectKey、GlobalKey、LabeledGlobalKey、GlobalObjectKey。整个key体系的代码还包括key.dart这个文件,里面包括Key、LocalKey、UniqueKey和ValueKey。Key是GlobalKey和LocalKey的抽象基类。LabeledGlobalKey和GlobalObjectKey是GlobalKey的抽象子类。ObjectKey、UniqueKey和ValueKey是LocalKey的三个具体子类。

1.2 widget

关于widget的代码274行到1922行。包括10个抽象类:Widget、StatelessWidget、StatefulWidget、ProxyWidget、ParentDataWidget、InheritedWidget、RenderObjectWidget、LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。这些类,大体可以把Widget分为组合式、渲染、功能性三种。

1.3 state

State的代码在823附近。State是一个抽象类。State首先起着一个枢纽的作用,它持有widget,也持有element。从state里获取context,只是简单返回持有的element。另一方面,State对外提供了widget的生命周期:initState、didUpdateWidget、reassemble、deactivate、activate、dispose、didChangeDependencies。这些生命周期方法是系统提供给我们的钩子。如果我们要主动发起渲染请求的话,就要调用State提供给我们的setState方法。而build则是我们告诉系统如何渲染这个widget的地方。前者提供时机,后者提供内容。

1.4 BuildContext

BuildContext是一个抽象类,代码位于2129-2485行。Element实现了BuildContext。

1.5 BuildOwner

BuildOwner位于2511-3168行。

1.6 element

element相关的代码位于3259行到6597行之间。接近一半的代码量,可以看出element是核心部分。

2 StatelessWidget和StatefulWidget

Widget是一个抽象类,里面关键的三个东西:

  1. final Key? key;
  2. Element createElement();
  3. static bool canUpdate(Widget oldWidget, Widget newWidget) {
  4. return oldWidget.runtimeType == newWidget.runtimeType
  5. && oldWidget.key == newWidget.key;
  6. }

我们要关注下canUpdate这个方法的实现,决定我们能否复用这个widget是由这个widget的runtimeType和key决定的。runtimeType表明了widget的类型,不同类型的widget是不能复用的。key是我们人为指定的一个值,它可以在同类型widget之间产生个体差异,以便我们或者渲染系统找到它。不设置的时候就是null,这时候只比较runtimeType就行。

我们先来介绍widget中的组合式子类:StatelessWidget和StatefulWidget。StatelessWidget创建的element是StatelessElement:

  1. @override
  2. StatelessElement createElement() => StatelessElement(this);

而StatefulWidget创建的是StatefulElement,并且还能创建state:

  1. @override
  2. StatefulElement createElement() => StatefulElement(this);
  3. @protected
  4. @factory
  5. State createState();

StatelessWidget和StatefulWidget仍然是抽象类,需要我们子类化。根据源码,我们发现StatefulWidget和StatelessWidget只负责创建对应element,并不持有它。而Statefulwidget只负责创建state,同样也并不持有它。

3 RenderObjectWidget

RenderObjectWidget有三个抽象子类LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。分别代表了没有孩子、只有一个孩子和有多个孩子的三种RenderObjectWidget。源码我们不再展开,想必你也猜到了是一个啥都没,一个有个child,最后一个有个children属性罢了。

相比于前面的StatelessWidget,RenderObjectWidget返回自己独特的RenderObjectElement,并且还多了一个RenderObject:

  1. RenderObject createRenderObject(BuildContext context);
  2. @protected
  3. void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  4. @protected
  5. void didUnmountRenderObject(covariant RenderObject renderObject) { }

RenderObjectWidget我们后面在RenderObjectElement的performRebuild里讲到了,它会调用updateRenderObject进行更新。这里我们无法展开讲updateRenderObject,需要去看具体子类里的实现。 

3.1 ColoredBox

我们以SingleChildRenderObjectWidget的一个具体子类ColoredBox为例:

  1. final Color color;
  2. @override
  3. RenderObject createRenderObject(BuildContext context) {
  4. return _RenderColoredBox(color: color);
  5. }
  6. @override
  7. void updateRenderObject(BuildContext context, RenderObject renderObject) {
  8. (renderObject as _RenderColoredBox).color = color;
  9. }

我们发现它创建的RenderObject是一个私有类_RenderColoredBox。而我们前面提到的updateRenderObject在这里只是设置一下新的颜色。

我们再转去看_RenderColoredBox的实现:

  1. class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
  2. _RenderColoredBox({ required Color color })
  3. : _color = color,
  4. super(behavior: HitTestBehavior.opaque);
  5. /// The fill color for this render object.
  6. ///
  7. /// This parameter must not be null.
  8. Color get color => _color;
  9. Color _color;
  10. set color(Color value) {
  11. if (value == _color) {
  12. return;
  13. }
  14. _color = value;
  15. markNeedsPaint();
  16. }
  17. @override
  18. void paint(PaintingContext context, Offset offset) {
  19. // It's tempting to want to optimize out this `drawRect()` call if the
  20. // color is transparent (alpha==0), but doing so would be incorrect. See
  21. // https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for
  22. // a good description of why.
  23. if (size > Size.zero) {
  24. context.canvas.drawRect(offset & size, Paint()..color = color);
  25. }
  26. if (child != null) {
  27. context.paintChild(child!, offset);
  28. }
  29. }
  30. }

主要是根据颜色在canvas上绘制背景色和child。设置新颜色会引起markNeedsPaint。markNeedsPaint相关代码在RenderObject里。

3.2 markNeedsPaint

  1. void markNeedsPaint() {
  2. if (_needsPaint) {
  3. return;
  4. }
  5. _needsPaint = true;
  6. // If this was not previously a repaint boundary it will not have
  7. // a layer we can paint from.
  8. if (isRepaintBoundary && _wasRepaintBoundary) {
  9. if (owner != null) {
  10. owner!._nodesNeedingPaint.add(this);
  11. owner!.requestVisualUpdate();
  12. }
  13. } else if (parent is RenderObject) {
  14. parent!.markNeedsPaint();
  15. } else {
  16. // If we are the root of the render tree and not a repaint boundary
  17. // then we have to paint ourselves, since nobody else can paint us.
  18. // We don't add ourselves to _nodesNeedingPaint in this case,
  19. // because the root is always told to paint regardless.
  20. //
  21. // Trees rooted at a RenderView do not go through this
  22. // code path because RenderViews are repaint boundaries.
  23. if (owner != null) {
  24. owner!.requestVisualUpdate();
  25. }
  26. }
  27. }

这里出现了新的Owner:PipelineOwner。markNeedsPaint标记自己需要重新绘制,如果自己是绘制边界,就把自己加入需要绘制的节点列表里。如果不是绘制边界,就调用父节点的markNeedsPaint。这里只是简单标记和放入列表,真正执行绘制的时机是在WidgetsBinding.drawFrame里的flushPaint:

  1. void drawFrame() {
  2. buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget
  3. super.drawFrame();
  4. //下面几个是在super.drawFrame()执行的
  5. pipelineOwner.flushLayout(); // 2.更新布局
  6. pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息
  7. pipelineOwner.flushPaint(); // 4.重绘
  8. if (sendFramesToEngine) {
  9. renderView.compositeFrame(); // 5. 上屏,将绘制出的bit数据发送给GPU
  10. }
  11. }

3.3 markNeedsLayout

上面的代码里,我们也看到了布局是在这之前的flushLayout执行的。RenderBox源码里PipelineOwner通过markNeedsLayout标记、收集需要布局节点:

  1. void markNeedsLayout() {
  2. if (_needsLayout) {
  3. return;
  4. }
  5. if (_relayoutBoundary == null) {
  6. _needsLayout = true;
  7. if (parent != null) {
  8. // _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
  9. // Conservatively mark everything dirty until it reaches the closest
  10. // known relayout boundary.
  11. markParentNeedsLayout();
  12. }
  13. return;
  14. }
  15. if (_relayoutBoundary != this) {
  16. markParentNeedsLayout();
  17. } else {
  18. _needsLayout = true;
  19. if (owner != null) {
  20. owner!._nodesNeedingLayout.add(this);
  21. owner!.requestVisualUpdate();
  22. }
  23. }
  24. }
  25. void markParentNeedsLayout() {
  26. assert(_debugCanPerformMutations);
  27. _needsLayout = true;
  28. assert(this.parent != null);
  29. final RenderObject parent = this.parent!;
  30. if (!_doingThisLayoutWithCallback) {
  31. parent.markNeedsLayout();
  32. } else {
  33. assert(parent._debugDoingThisLayout);
  34. }
  35. assert(parent == this.parent);
  36. }

我们发现布局标记和绘制标记的实现是类似的,都需要标记自身,都需要向上寻找布局或者绘制的边界。PipelineOwner最终对其调用performLayout和markNeedsPaint:

  1. void flushLayout() {
  2. try {
  3. while (_nodesNeedingLayout.isNotEmpty) {
  4. final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
  5. _nodesNeedingLayout = <RenderObject>[];
  6. dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
  7. for (int i = 0; i < dirtyNodes.length; i++) {
  8. if (_shouldMergeDirtyNodes) {
  9. _shouldMergeDirtyNodes = false;
  10. if (_nodesNeedingLayout.isNotEmpty) {
  11. _nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));
  12. break;
  13. }
  14. }
  15. final RenderObject node = dirtyNodes[i];
  16. if (node._needsLayout && node.owner == this) {
  17. node._layoutWithoutResize();
  18. }
  19. }
  20. // No need to merge dirty nodes generated from processing the last
  21. // relayout boundary back.
  22. _shouldMergeDirtyNodes = false;
  23. }
  24. for (final PipelineOwner child in _children) {
  25. child.flushLayout();
  26. }
  27. } finally {
  28. _shouldMergeDirtyNodes = false;
  29. }
  30. }
  31. void _layoutWithoutResize() {
  32. RenderObject? debugPreviousActiveLayout;
  33. try {
  34. performLayout();
  35. markNeedsSemanticsUpdate();
  36. } catch (e, stack) {
  37. _reportException('performLayout', e, stack);
  38. }
  39. _needsLayout = false;
  40. markNeedsPaint();
  41. }

performLayout这个方法在RenderBox里实现为空,需要子类自行实现。

前面ColoredBox这个例子里我们在updateRenderObject里改变颜色并不会引起布局变化。现在我们找一个RenderPositionedBox的源码来看看。

3.3 RenderPositionedBox

RenderPositionedBox是Align使用的renderObject。我们看看它的updateRenderObject实现:

  1. void updateRenderObject(BuildContext context, RenderPositionedBox renderObject) {
  2. renderObject
  3. ..alignment = alignment
  4. ..widthFactor = widthFactor
  5. ..heightFactor = heightFactor
  6. ..textDirection = Directionality.maybeOf(context);
  7. }

再进到RenderPositionedBox的set alignment实现:

  1. set alignment(AlignmentGeometry value) {
  2. if (_alignment == value) {
  3. return;
  4. }
  5. _alignment = value;
  6. _markNeedResolution();
  7. }
  8. void _markNeedResolution() {
  9. _resolvedAlignment = null;
  10. markNeedsLayout();
  11. }

我们发现设置新的alignment,会引起markNeedsLayout的调用。

4 三个功能性widget:ProxyWidget、ParentDataWidget、InheritedWidget

暂不展开,详见透过源码理解Flutter InheritedWidget_Mamong的博客-CSDN博客

5 StatefulElement和StatelessElement

我们再跳到StatelessElement构造方法:

  1. class StatelessElement extends ComponentElement {
  2. /// Creates an element that uses the given widget as its configuration.
  3. StatelessElement(StatelessWidget super.widget);
  4. @override
  5. Widget build() => (widget as StatelessWidget).build(this);
  6. }

StatelessElement自身可以通过build返回子element对应的widget。

而StatefulElement构造方法:

  1. /// Creates an element that uses the given widget as its configuration.
  2. StatefulElement(StatefulWidget widget)
  3. : _state = widget.createState(),
  4. super(widget) {
  5. state._element = this;
  6. state._widget = widget;
  7. }

我们发现在创建element的时候,会先调用widget的createState创建state,并指向它,然后state就伸出两只手,一只手拉着widget,另一只手拉着element。element里面有一个重要的方法:

  Widget build() => state.build(this);

这里我们可以认为state build出来的是element持有的widget的“child”。事实上,无论StatelessElement还是Statefulwidget,它们都没child这个概念,但是对应的element是有一个child的属性的。所以我们姑且这么看待它们的关系。这里把element传进去,只是因为我们可能需要用到element树一些上下文信息。

3.1 setState

现在看看我们的老朋友,state里的setState方法的实现:

  1. void setState(VoidCallback fn) {
  2. _element!.markNeedsBuild();
  3. }
  4. void markNeedsBuild() {
  5. if (dirty) {
  6. return;
  7. }
  8. _dirty = true;
  9. owner!.scheduleBuildFor(this);
  10. }
  11. void scheduleBuildFor(Element element) {
  12. _dirtyElements.add(element);
  13. element._inDirtyList = true;
  14. }
  15. void rebuild() {
  16. performRebuild();
  17. }
  18. void performRebuild() {
  19. _dirty = false;
  20. }

完整的流程如下:

中间省略一些代码,我们直接跳到performRebuild实现。对于基类Element的实现,只是简单标记为dirty。Element分为渲染和组件两种类型,前者与渲染相关,后者用于组成其他element。

3.2 performRebuild

对于跟渲染相关的RenderObjectElement的performRebuild,则需要更新它的renderObject:

  1. void _performRebuild() {
  2. (widget as RenderObjectWidget).updateRenderObject(this, renderObject);
  3. super.performRebuild(); // clears the "dirty" flag
  4. }

对于跟组件相关的ComponentElement的performRebuild实现:

  1. void performRebuild() {
  2. Widget? built;
  3. try {
  4. built = build();
  5. } catch (e, stack) {
  6. } finally {
  7. // We delay marking the element as clean until after calling build() so
  8. // that attempts to markNeedsBuild() during build() will be ignored.
  9. super.performRebuild(); // clears the "dirty" flag
  10. }
  11. try {
  12. _child = updateChild(_child, built, slot);
  13. } catch (e, stack) {
  14. _child = updateChild(null, built, slot);
  15. }
  16. }

这里的核心是会调用build方法创建新的widget,然后使用这个widget去更新child element。从前面的代码中我们可以看到statefulElement和statelessElement的build实现是有差异的。但是返回的widget,其实都是它们的child对应的widget。在多个渲染周期,child element会一直存在,而需要更新时widget就会重新创建。更新后的Element设置为当前Element的child。至于怎么更新,我们等下再讲。

ComponentElement是ProxyElement、StatefulElement和StatelessElement的父类。但是只有StatefulElement覆写了performRebuild。进一步来到StatefulElement的performRebuild实现:

  1. void performRebuild() {
  2. if (_didChangeDependencies) {
  3. state.didChangeDependencies();
  4. _didChangeDependencies = false;
  5. }
  6. super.performRebuild();
  7. }

StatefulElement增加的任务是如果依赖发生了变化,要触发state的didChangeDependencies方法。

3.3 updateChild

回到前文,我们再来看Element的updateChild实现:

  1. Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  2. // 如果'newWidget'为null,而'child'不为null,那么我们删除'child',返回null。
  3. if (newWidget == null) {
  4. if (child != null) {
  5. deactivateChild(child);
  6. }
  7. return null;
  8. }
  9. final Element newChild;
  10. if (child != null) {
  11. // 两个widget相同,位置不同更新位置。先更新位置,然后返回child。这里比较的是hashCode
  12. if (child.widget == newWidget) {
  13. if (child.slot != newSlot) {
  14. updateSlotForChild(child, newSlot);
  15. }
  16. newChild = child;
  17. } else if (Widget.canUpdate(child.widget, newWidget)) {
  18. //两个widget不同,但是可以复用。位置不同则先更新位置。然后用新widget更新element
  19. if (child.slot != newSlot) {
  20. updateSlotForChild(child, newSlot);
  21. }
  22. child.update(newWidget);
  23. newChild = child;
  24. } else {
  25. // 如果无法更新复用,那么删除原来的child,然后创建一个新的Element并返回。
  26. deactivateChild(child);
  27. newChild = inflateWidget(newWidget, newSlot);
  28. }
  29. } else {
  30. // 如果是初次创建,那么创建一个新的Element并返回。
  31. newChild = inflateWidget(newWidget, newSlot);
  32. }
  33. return newChild;
  34. }

这里关键的两个方法是update和inflateWidget。

3.4 update

对于不同类型的child的update方法是不一样的。基类Element只是用新的替换旧的而已:

  1. void update(covariant Widget newWidget) {
  2. _widget = newWidget;
  3. }

对于StatelessElement的update,就是直接更换widget:

  1. @override
  2. void update(StatelessWidget newWidget) {
  3. //直接更换widget
  4. super.update(newWidget);
  5. assert(widget == newWidget);
  6. rebuild(force: true);
  7. }

对于StatefulElement的update,除了更换widget,还要更换state指向的widget:

  1. void update(StatefulWidget newWidget) {
  2. super.update(newWidget);
  3. final StatefulWidget oldWidget = state._widget!;
  4. state._widget = widget as StatefulWidget;
  5. final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
  6. rebuild(force: true);
  7. }

最后都通过调用rebuild,标记自身dirty。

对于SingleChildRenderObjectElement就是对它的child调用updateChild,对于MultiChildRenderObjectElement就是对它的children调用updateChildren:

  1. void update(SingleChildRenderObjectWidget newWidget) {
  2. super.update(newWidget);
  3. _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  4. }
  5. void update(MultiChildRenderObjectWidget newWidget) {
  6. super.update(newWidget);
  7. final MultiChildRenderObjectWidget multiChildRenderObjectWidget = widget as MultiChildRenderObjectWidget;
  8. _children = updateChildren(_children, multiChildRenderObjectWidget.children, forgottenChildren: _forgottenChildren);
  9. _forgottenChildren.clear();
  10. }

而对于ProxyElement主要是更新widget和通知:

  1. @override
  2. void update(ProxyWidget newWidget) {
  3. final ProxyWidget oldWidget = widget as ProxyWidget;
  4. //使用新的widget更新持有的widget
  5. super.update(newWidget);
  6. //通知其他关联widget自己发生了变化
  7. updated(oldWidget);
  8. //标记dirty
  9. rebuild(force: true);
  10. }
  11. @protected
  12. void updated(covariant ProxyWidget oldWidget) {
  13. notifyClients(oldWidget);
  14. }

3.5 updateChildren

updateChild前面我们已经提到了,而对于updateChildren的实现:

  1. List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) {
  2. Element? replaceWithNullIfForgotten(Element child) {
  3. return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
  4. }
  5. Object? slotFor(int newChildIndex, Element? previousChild) {
  6. return slots != null
  7. ? slots[newChildIndex]
  8. : IndexedSlot<Element?>(newChildIndex, previousChild);
  9. }
  10. // This attempts to diff the new child list (newWidgets) with
  11. // the old child list (oldChildren), and produce a new list of elements to
  12. // be the new list of child elements of this element. The called of this
  13. // method is expected to update this render object accordingly.
  14. // The cases it tries to optimize for are:
  15. // - the old list is empty
  16. // - the lists are identical
  17. // - there is an insertion or removal of one or more widgets in
  18. // only one place in the list
  19. // If a widget with a key is in both lists, it will be synced.
  20. // Widgets without keys might be synced but there is no guarantee.
  21. // The general approach is to sync the entire new list backwards, as follows:
  22. // 1. Walk the lists from the top, syncing nodes, until you no longer have
  23. // matching nodes.
  24. // 2. Walk the lists from the bottom, without syncing nodes, until you no
  25. // longer have matching nodes. We'll sync these nodes at the end. We
  26. // don't sync them now because we want to sync all the nodes in order
  27. // from beginning to end.
  28. // At this point we narrowed the old and new lists to the point
  29. // where the nodes no longer match.
  30. // 3. Walk the narrowed part of the old list to get the list of
  31. // keys and sync null with non-keyed items.
  32. // 4. Walk the narrowed part of the new list forwards:
  33. // * Sync non-keyed items with null
  34. // * Sync keyed items with the source if it exists, else with null.
  35. // 5. Walk the bottom of the list again, syncing the nodes.
  36. // 6. Sync null with any items in the list of keys that are still
  37. // mounted.
  38. int newChildrenTop = 0;
  39. int oldChildrenTop = 0;
  40. int newChildrenBottom = newWidgets.length - 1;
  41. int oldChildrenBottom = oldChildren.length - 1;
  42. final List<Element> newChildren = List<Element>.filled(newWidgets.length, _NullElement.instance);
  43. Element? previousChild;
  44. // 从前往后依次对比,相同的更新Element,记录位置,直到不相等时跳出循环。.
  45. while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  46. final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
  47. final Widget newWidget = newWidgets[newChildrenTop];
  48. // 注意这里的canUpdate,本例中在没有添加key时返回true。
  49. // 因此直接执行updateChild,本循环结束返回newChildren。后面因条件不满足都在不执行。
  50. // 一旦添加key,这里返回false,不同之处就此开始。
  51. if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) {
  52. break;
  53. }
  54. final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  55. newChildren[newChildrenTop] = newChild;
  56. previousChild = newChild;
  57. newChildrenTop += 1;
  58. oldChildrenTop += 1;
  59. }
  60. // 从后往前依次对比,记录位置,直到不相等时跳出循环。
  61. while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  62. final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
  63. final Widget newWidget = newWidgets[newChildrenBottom];
  64. if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) {
  65. break;
  66. }
  67. oldChildrenBottom -= 1;
  68. newChildrenBottom -= 1;
  69. }
  70. // 至此,就可以得到新旧List中不同Weiget的范围。
  71. final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
  72. Map<Key, Element>? oldKeyedChildren;
  73. // 如果存在中间范围,扫描旧children,获取所有的key与Element保存至oldKeyedChildren。
  74. if (haveOldChildren) {
  75. oldKeyedChildren = <Key, Element>{};
  76. while (oldChildrenTop <= oldChildrenBottom) {
  77. final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
  78. if (oldChild != null) {
  79. if (oldChild.widget.key != null) {
  80. oldKeyedChildren[oldChild.widget.key!] = oldChild;
  81. } else {
  82. deactivateChild(oldChild);
  83. }
  84. }
  85. oldChildrenTop += 1;
  86. }
  87. }
  88. // 更新中间不同的部分,如果新旧key相同就更新一下重新利用,否则新的widget就没有旧的对应,是插入行为
  89. while (newChildrenTop <= newChildrenBottom) {
  90. Element? oldChild;
  91. final Widget newWidget = newWidgets[newChildrenTop];
  92. if (haveOldChildren) {
  93. final Key? key = newWidget.key;
  94. if (key != null) {
  95. // key不为null,通过key获取对应的旧Element
  96. oldChild = oldKeyedChildren![key];
  97. if (oldChild != null) {
  98. if (Widget.canUpdate(oldChild.widget, newWidget)) {
  99. // we found a match!
  100. // remove it from oldKeyedChildren so we don't unsync it later
  101. oldKeyedChildren.remove(key);
  102. } else {
  103. // Not a match, let's pretend we didn't see it for now.
  104. oldChild = null;
  105. }
  106. }
  107. }
  108. }
  109. final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  110. newChildren[newChildrenTop] = newChild;
  111. previousChild = newChild;
  112. newChildrenTop += 1;
  113. }
  114. // We've scanned the whole list.
  115. // 重置
  116. newChildrenBottom = newWidgets.length - 1;
  117. oldChildrenBottom = oldChildren.length - 1;
  118. // 将后面相同的Element更新后添加到newChildren,至此形成新的完整的children。
  119. while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  120. final Element oldChild = oldChildren[oldChildrenTop];
  121. final Widget newWidget = newWidgets[newChildrenTop];
  122. final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  123. newChildren[newChildrenTop] = newChild;
  124. previousChild = newChild;
  125. newChildrenTop += 1;
  126. oldChildrenTop += 1;
  127. }
  128. // 清除旧列表中多余的带key的Element
  129. if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
  130. for (final Element oldChild in oldKeyedChildren.values) {
  131. if (forgottenChildren == null || !forgottenChildren.contains(oldChild)) {
  132. deactivateChild(oldChild);
  133. }
  134. }
  135. }
  136. return newChildren;
  137. }

dif算法相对比较复杂,可能理解起来比较困难。值得一提的是,无论 updateChild还是updateChildren都实现在基类element里。同层diff算法里使用key并不是出于性能考虑,没有key能够就地复用,使用key能够指定复用对象。有时候就地复用会有一些问题,譬如某个widget自身有一些状态,你如果就地复用其他widget,就会导致这些状态的丢失。

3.6 inflateWidget

再来看看inflateWidget的实现,它主要是用来创建新的element,并且mount。如果widget有GlobalKey的话,则会尝试获取对应的element,然后更新后返回。

  1. Element inflateWidget(Widget newWidget, Object? newSlot) {
  2. try {
  3. //如果widget带key,并且是GlobalKey,则尝试获取一下对应的element,并用新的widget更新它然后返回
  4. final Key? key = newWidget.key;
  5. if (key is GlobalKey) {
  6. final Element? newChild = _retakeInactiveElement(key, newWidget);
  7. if (newChild != null) {
  8. newChild._activateWithParent(this, newSlot);
  9. final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
  10. return updatedChild!;
  11. }
  12. }
  13. // 这里就调用到了createElement,重新创建了Element
  14. final Element newChild = newWidget.createElement();
  15. newChild.mount(this, newSlot);
  16. return newChild;
  17. }
  18. }

3.7 mount

我们再来看看element基类的mount:

  1. void mount(Element? parent, Object? newSlot) {
  2. _parent = parent;
  3. _slot = newSlot;
  4. _lifecycleState = _ElementLifecycle.active;
  5. _depth = _parent != null ? _parent!.depth + 1 : 1;
  6. if (parent != null) {
  7. // Only assign ownership if the parent is non-null. If parent is null
  8. // (the root node), the owner should have already been assigned.
  9. // See RootRenderObjectElement.assignOwner().
  10. _owner = parent.owner;
  11. }
  12. final Key? key = widget.key;
  13. if (key is GlobalKey) {
  14. owner!._registerGlobalKey(key, this);
  15. }
  16. _updateInheritance();
  17. attachNotificationTree();
  18. }

mount就是将自身插入父element的某个slot中。我们发现Element在mount的时候,会将父element的ower设置给自己。如果widget带有key,那么ower会将这个element注册到自己的map里。

而对于组合式Element的mount有所差异,除了上述基类行为,还会调用_firstBuild:

  1. @override
  2. void mount(Element? parent, Object? newSlot) {
  3. super.mount(parent, newSlot);
  4. _firstBuild();
  5. }
  6. void _firstBuild() {
  7. // StatefulElement overrides this to also call state.didChangeDependencies.
  8. rebuild(); // This eventually calls performRebuild.
  9. }

对于StatelessElement,_firstBuild的实现只是单纯rebuild一下。而对于StatefulElement:

  1. @override
  2. void _firstBuild() {
  3. final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
  4. state.didChangeDependencies();
  5. super._firstBuild();
  6. }

我们发现_firstBuild里调用了state的initState方法,这里说明我们在state里实现的生命周期方法,其实会被StatefulElement根据自身的不同状态而调用。因此其他方法我们不再赘述。

3.8 why?

在参考文章里有一个问题,我们来分析一下,增加我们对本文的理解程度。现在我们有如下一段代码:

  1. import 'dart:math';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. void main() => runApp(MyApp());
  5. class MyApp extends StatelessWidget {
  6. @override
  7. Widget build(BuildContext context) {
  8. return MaterialApp(
  9. title: 'Flutter Demo',
  10. theme: ThemeData(
  11. primarySwatch: Colors.blue,
  12. ),
  13. home: MyHomePage(title: 'Home Page'),
  14. );
  15. }
  16. }
  17. class MyHomePage extends StatefulWidget {
  18. MyHomePage({Key key, this.title}) : super(key: key);
  19. final String title;
  20. @override
  21. _MyHomePageState createState() => _MyHomePageState();
  22. }
  23. class _MyHomePageState extends State<MyHomePage> {
  24. List<Widget> widgets;
  25. @override
  26. void initState() {
  27. super.initState();
  28. widgets = [
  29. StatelessColorfulTile(),
  30. StatelessColorfulTile()
  31. ];
  32. }
  33. @override
  34. Widget build(BuildContext context) {
  35. return Scaffold(
  36. appBar: AppBar(
  37. title: Text(widget.title),
  38. ),
  39. body: Row(
  40. children: widgets,
  41. ),
  42. floatingActionButton: FloatingActionButton(
  43. child: Icon(Icons.refresh),
  44. onPressed: _swapTile,
  45. ),
  46. );
  47. }
  48. _swapTile() {
  49. setState(() {
  50. widgets.insert(1, widgets.removeAt(0));
  51. });
  52. }
  53. }
  54. class StatelessColorfulTile extends StatelessWidget {
  55. final Color _color = Utils.randomColor();
  56. @override
  57. Widget build(BuildContext context) {
  58. return Container(
  59. height: 150,
  60. width: 150,
  61. color: _color,
  62. );
  63. }
  64. }
  65. class Utils {
  66. static Color randomColor() {
  67. var red = Random.secure().nextInt(255);
  68. var greed = Random.secure().nextInt(255);
  69. var blue = Random.secure().nextInt(255);
  70. return Color.fromARGB(255, red, greed, blue);
  71. }
  72. }

代码可以直接复制到DartPad中运行查看效果。 或者点击这里直接运行

效果很简单,就是两个彩色方块,点击右下角的按钮后交换两个方块的位置。上面的方块是StatelessWidget,那我们把它换成StatefulWidget呢?。

  1. class StatefulColorfulTile extends StatefulWidget {
  2. StatefulColorfulTile({Key key}) : super(key: key);
  3. @override
  4. StatefulColorfulTileState createState() => StatefulColorfulTileState();
  5. }
  6. class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  7. final Color _color = Utils.randomColor();
  8. @override
  9. Widget build(BuildContext context) {
  10. return Container(
  11. height: 150,
  12. width: 150,
  13. color: _color,
  14. );
  15. }
  16. }

 再次执行代码,发现方块没有“交换”。这是为什么?结论是widget层面而言,两个widget的确发生了交换,但是Element并没有发生交换,原来位置的Element持有的state build出原来颜色的Container。

6 key

可以看参考,这里暂不展开

7 BuildOwner

buildOwner是framework这些代码背后的大boss。我们来看看它做了哪些事情。每个element都指向一个Owner用来维护它的生命周期:

  1. BuildOwner? get owner => _owner;
  2. BuildOwner? _owner;

为什么我们能用globalKey找到对应的element,没有什么神奇的,因为buildOwner有一个map维护着globalKey和element的对应关系:

  1. final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
  2. void _registerGlobalKey(GlobalKey key, Element element)
  3. void _unregisterGlobalKey(GlobalKey key, Element element)

buildOwner另一个作用是维护着element的build列表:

  1. final List<Element> _dirtyElements = <Element>[];
  2. void scheduleBuildFor(Element element) {
  3. if (element._inDirtyList) {
  4. _dirtyElementsNeedsResorting = true;
  5. return;
  6. }
  7. if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
  8. _scheduledFlushDirtyElements = true;
  9. onBuildScheduled!();
  10. }
  11. _dirtyElements.add(element);
  12. element._inDirtyList = true;
  13. }

WidgetsBinding会通过WidgetsBinding.drawFrame调用buildOwner的buildScope:

  1. void drawFrame() {
  2. buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget
  3. super.drawFrame();
  4. //下面几个是在super.drawFrame()执行的
  5. pipelineOwner.flushLayout(); // 2.更新布局
  6. pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息
  7. pipelineOwner.flushPaint(); // 4.重绘
  8. if (sendFramesToEngine) {
  9. renderView.compositeFrame(); // 5. 上屏,将绘制出的bit数据发送给GPU
  10. }
  11. }

buildScope对_dirtyElements里的element调用rebuild:

  1. void buildScope(Element context, [ VoidCallback? callback ]) {
  2. if (callback == null && _dirtyElements.isEmpty) {
  3. return;
  4. }
  5. try {
  6. _scheduledFlushDirtyElements = true;
  7. if (callback != null) {
  8. _dirtyElementsNeedsResorting = false;
  9. try {
  10. callback();
  11. }
  12. }
  13. _dirtyElements.sort(Element._sort);
  14. _dirtyElementsNeedsResorting = false;
  15. int dirtyCount = _dirtyElements.length;
  16. int index = 0;
  17. while (index < dirtyCount) {
  18. final Element element = _dirtyElements[index];
  19. try {
  20. element.rebuild();
  21. }
  22. index += 1;
  23. if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
  24. _dirtyElements.sort(Element._sort);
  25. _dirtyElementsNeedsResorting = false;
  26. dirtyCount = _dirtyElements.length;
  27. while (index > 0 && _dirtyElements[index - 1].dirty) {
  28. // It is possible for previously dirty but inactive widgets to move right in the list.
  29. // We therefore have to move the index left in the list to account for this.
  30. // We don't know how many could have moved. However, we do know that the only possible
  31. // change to the list is that nodes that were previously to the left of the index have
  32. // now moved to be to the right of the right-most cleaned node, and we do know that
  33. // all the clean nodes were to the left of the index. So we move the index left
  34. // until just after the right-most clean node.
  35. index -= 1;
  36. }
  37. }
  38. }
  39. } finally {
  40. for (final Element element in _dirtyElements) {
  41. assert(element._inDirtyList);
  42. element._inDirtyList = false;
  43. }
  44. _dirtyElements.clear();
  45. _scheduledFlushDirtyElements = false;
  46. _dirtyElementsNeedsResorting = null;
  47. }
  48. }

后面的流程就回到了我们前面的performRebuild方法 。

8 没有覆盖的内容

本文没有提及具体的布局逻辑,将在后面的文章里进行讲述。

9 图示

文中出现的一些关键类的继承关系:

参考

1.说说Flutter中最熟悉的陌生人 —— Key_flutter globalkey 源码_唯鹿的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/313322
推荐阅读