Flutter之setState流程源码解析

630 阅读16分钟

一、flutter之widget的setState更新操作

**1.1、**跟源码:

setState(() {
  tiles.insert(1, tiles.removeAt(0));
});

**1.2、**State类中的

@protected
void setState(VoidCallback fn) {
	....
	//只关注这个
  _element!.markNeedsBuild();
}

**1.3、**Element 类中的markNeedsBuild()

void markNeedsBuild() {
  if (_lifecycleState != _ElementLifecycle.active)
    return;
    ....
  if (dirty)
    return;
  _dirty = true;
  owner!.scheduleBuildFor(this);
}

将元素标记为"脏元素"( flutter 在更新触发重新渲染时,只会将标脏了的元素重新绘制渲)同时调用 BuildOwner 的 scheduleBuildFor 方法

**1.4、**BuildOwner类的scheduleBuildFor(Element element),加脏元素,同时调用 BuildOwner类的 onBuildScheduled 方法

void scheduleBuildFor(Element element) {
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled!();//调用
  }
  //核心,将脏元素加入集合
  _dirtyElements.add(element);
  element._inDirtyList = true;
  assert(() {
    if (debugPrintScheduleBuildForStacks)
      debugPrint('...dirty list is now: $_dirtyElements');
    return true;
  }());
}

**1.5、**onBuildScheduled 方法是WidgetsBinding 中的 关注 这一段 buildOwner!.onBuildScheduled = _handleBuildScheduled;

image.png

void initInstances() {
  super.initInstances();
  _instance = this;

  assert(() {
    _debugAddStackFilters();
    return true;
  }());

  // Initialization of [_buildOwner] has to be done after
  // [super.initInstances] is called, as it requires [ServicesBinding] to
  // properly setup the [defaultBinaryMessenger] instance.
  _buildOwner = BuildOwner();
  //主要关注这里
  buildOwner!.onBuildScheduled = _handleBuildScheduled;
  window.onLocaleChanged = handleLocaleChanged;
  window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
  SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
  FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}

**1.6、**通过onBuildScheduled 触发WidgetsBinding 中的_handleBuildScheduled()方法

void _handleBuildScheduled() {

  ensureVisualUpdate();
}

**1.7、**SchedulerBinding 类的 ensureVisualUpdate 方法ensureVisualUpdate判断当前调度所处的状态,如果是 idle(空闲)或 postFrameCallbacks 时调用 scheduleFrame

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();//主要是关注该方法
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

**1.8、**SchedulerBinding类的scheduleFrame()方法,该类中主要关注这两行:

  • ensureFrameCallbacksRegistered,为了注册回调 window.scheduleFrame();方法事件
  • window.scheduleFrame();
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;
  assert(() {
    if (debugPrintScheduleFrameStacks)
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    return true;
  }());
  ensureFrameCallbacksRegistered();
  window.scheduleFrame();
  _hasScheduledFrame = true;
}

**1.9、**首先跳过ensureFrameCallbacksRegistered 看 SingletonFlutterWindow类中的 window.scheduleFrame();方法

他是一个函数参数对象赋值

void scheduleFrame() => platformDispatcher.scheduleFrame();

至于platformDispatcher.scheduleFrame()它是一个native方法, 是底部 dart engine 底层的一个方法,这个方法用于 注册 vsync 信号的监听。由此可见,setState 方法的主要原理是,将当前元素标脏,同时触发 vsync 信号,以便在下次 vsync 信号回调时,完成这些脏元素的更新。

void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

**1.10、**到了native方法断了不知道怎么更了,这就回到上面的ensureFrameCallbacksRegistered方法

​ **1.9中的 scheduleFrame()**方法会回调到Window Flutter Framework连接宿主操作系统的接口, 在 Window 类 中定义了Vsync 信号的回调处理

主要看PlatformDispatcher类中的下面这一段代码:

VoidCallback? get onDrawFrame => _onDrawFrame;
VoidCallback? _onDrawFrame;
Zone _onDrawFrameZone = Zone.root;
set onDrawFrame(VoidCallback? callback) {
  _onDrawFrame = callback;
  _onDrawFrameZone = Zone.current;
}

**1.11、**既然回调到onDrawFrame => _onDrawFrame 那就主要就是看onDrawFrame(VoidCallback? callback) 在哪注册了:

这就说到了我们上面跳过的一个方法SchedulerBinding类中的ensureFrameCallbacksRegistered方法

@protected
void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame; //赋值_handleDrawFrame方法,所以上面回调的时候会触发_handleDrawFrame方法
}

**1.12、**看_handleDrawFrame方法的话就看SchedulerBinding类中的 _handleDrawFrame中的 handleDrawFrame();方法

void _handleDrawFrame() {
  if (_rescheduleAfterWarmUpFrame) {
    _rescheduleAfterWarmUpFrame = false;
    // Reschedule in a post-frame callback to allow the draw-frame phase of
    // the warm-up frame to finish.
    addPostFrameCallback((Duration timeStamp) {
      // Force an engine frame.
      //
      // We need to reset _hasScheduledFrame here because we cancelled the
      // original engine frame, and therefore did not run handleBeginFrame
      // who is responsible for resetting it. So if a frame callback set this
      // to true in the "begin frame" part of the warm-up frame, it will
      // still be true here and cause us to skip scheduling an engine frame.
      _hasScheduledFrame = false;
      scheduleFrame();
    });
    return;
  }
  handleDrawFrame();//这里
}

**1.13、**再看SchedulerBinding类中handleDrawFrame()

void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // end the "Animate" phase
  try {
    // PERSISTENT FRAME CALLBACKS
   	
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
     //persistentCallbacks用于存放一些持久的回调
    //_persistentCallbacks 在Frame结束时调用,类似每一次的setstatus操作--_persistentCallbacks
    for (final FrameCallback callback in _persistentCallbacks)// _persistentCallbacks,,在Frame结束时调用,类似每一次的setstatus操作,调用后会被系统移除
    //遍历执行方法回调
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
  
     //postFrameCallbacks用于存放一些持久的回调
    /_postFrameCallback 在build结束后调用一次
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    final List<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);//_postFrameCallbacks  //在Frame结束时只会被调用一次,调用后会被系统移除,
    _postFrameCallbacks.clear();
    
    for (final FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
    _schedulerPhase = SchedulerPhase.idle;
    Timeline.finishSync(); // end the Frame
    assert(() {
      if (debugPrintEndFrameBanner)
        debugPrint('▀' * _debugBanner!.length);
      _debugBanner = null;
      return true;
    }());
    _currentFrameTimeStamp = null;
  }
}

在这个方法中,主要是执行一些回调集合,其中(可以参考我的学习项目:first_flutter_project:CanvasPointScroll2.dart 文件中的使用)

persistentCallbacks:用于存放一些持久的回调,如下代码所示SchedulerBinding.instance.addPersistentFrameCallback(),这个回调中处理了布局与绘制工作,即 调用执行 build/layout/paint 流水线工作的地方,

postFrameCallbacks:在Frame结束时只会被调用一次,调用后会被系统移除,可由 SchedulerBinding.instance.addPostFrameCallback() 注册(相当于监听画面是否绘制完成,首次build结束后 只会调用一次可以获取widget宽高)

**1.14、**上一节有两个方法persistentCallbacks和postFrameCallbacks我们来看看这两个方法:上面说到这是回调,那就要看看在哪注册的

  • 首先看_persistentCallbacks 跟踪发现是在 RendererBinding的initInstances()方法中的addPersistentFrameCallback()方法调用注册

    注册了_handlePersistentFrameCallback方法

void initInstances() {
  super.initInstances();
  _instance = this;
  _pipelineOwner = PipelineOwner(
    onNeedVisualUpdate: ensureVisualUpdate,
    onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
  );
  window
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsAction = _handleSemanticsAction;
  initRenderView();
  _handleSemanticsEnabledChanged();
  assert(renderView != null);
  addPersistentFrameCallback(_handlePersistentFrameCallback);//这里
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame);
  }
}

_handlePersistentFrameCallback方法如下,主要看他的 drawFrame()方法

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

因为_persistentCallbacks 是回调所以drawFrame();方法要看它的具体实现的子类:WidgetsBinding类,它实现了RendererBinding,所以回调先走子类的方法

image.png

**1.15、**所以看WidgetsBinding类中的buildOwner!.buildScope(renderViewElement!);方法主要是这个方法

@override
void drawFrame() {
  assert(!debugBuildingDirtyElements);
  assert(() {
    debugBuildingDirtyElements = true;
    return true;
  }());

  TimingsCallback? firstFrameCallback;
  if (_needToReportFirstFrame) {
    assert(!_firstFrameCompleter.isCompleted);

    firstFrameCallback = (List<FrameTiming> timings) {
      assert(sendFramesToEngine);
      if (!kReleaseMode) {
        developer.Timeline.instantSync('Rasterized first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
      }
      SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
      firstFrameCallback = null;
      _firstFrameCompleter.complete();
    };
    // Callback is only invoked when FlutterView.render is called. When
    // sendFramesToEngine is set to false during the frame, it will not be
    // called and we need to remove the callback (see below).
    SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
  }

  try {
    if (renderViewElement != null)
      buildOwner!.buildScope(renderViewElement!);
      
    super.drawFrame();//走RendererBinding的
    buildOwner!.finalizeTree();
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
  if (!kReleaseMode) {
    if (_needToReportFirstFrame && sendFramesToEngine) {
      developer.Timeline.instantSync('Widgets built first useful frame');
    }
  }
  _needToReportFirstFrame = false;
  if (firstFrameCallback != null && !sendFramesToEngine) {
    // This frame is deferred and not the first frame sent to the engine that
    // should be reported.
    _needToReportFirstFrame = true;
    SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
  }
}

**1.16、**至于上面说的RendererBinding的drawFrame()方法它,在WidgetsBinding类中使用了super.drawFrame();//走RendererBinding的

@protected
void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

mixin widgetBinding 以 RenderingBinding 为基础,因此会覆盖 RendererBinding 的drawFrame 函数,该方法执行的操作,其实就是 build -> layout -> paint -> composite

1.17、后面就是走1.15中说到的buildScope(renderViewElement!)方法,该方法在super.drawFrame() 前面其实就是通过native层回调到 widget的buildScope方法

​ 该方法主要为了重新给树排序

**1.18、**BuildOwner类中的buildScope 方法,

void buildScope(Element context, [VoidCallback callback]) {
    ...
    try {
		...
		//1.排序
      _dirtyElements.sort(Element._sort);
     	...
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        try {
        	//2.遍历rebuild
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
        }
        index += 1;
      }
    } finally {
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      //3.清空
      _dirtyElements.clear();
		...
    }
  }

上面代码主要分为下面三段

1.18.1步:按照Element的深度从小到大,对_dirtyElements进行排序

​ 为啥要排序呢?因为父Widget的build方法必然会触发子Widget的build,如果先build了子Widget,后面再build父Widget时,子Widget又要被 build一次。所以这样排序之后,可以避免子Widget的重复build。

1.18.2步:遍历执行_dirtyElements当中element的rebuild方法

​ 值得一提的是,遍历执行的过程中,也有可能会有新的element被加入到_dirtyElements集合中,此时会根据dirtyElements集合的长度判断是否 有新的元素进来了,如果有,就重新排序。element的rebuild方法最终会调用performRebuild(),而performRebuild()不同的Element有不同的实现

1.18.3步:遍历结束之后,清空dirtyElements集合

1.19、上面1.18中的_dirtyElements[index].rebuild();这里方法最终会调用performRebuild()**,而performRebuild()不同的Element有不同的实现

是在Element类中的rebuild() 方法,他其中的 performRebuild();是关键

void rebuild() {
  assert(_lifecycleState != _ElementLifecycle.initial);
  if (_lifecycleState != _ElementLifecycle.active || !_dirty)
    return;
 ...
 ..
  Element? debugPreviousBuildTarget;
 ...
  performRebuild();
  assert(() {
    assert(owner!._debugCurrentBuildTarget == this);
    owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
    return true;
  }());
  assert(!_dirty);
}

**1.20、**因为Element类是基类,所以主要看它的实现子类,performRebuild()不同的Element有不同的实现,我们暂时只看最常用的两个Element:

  • ComponentElement,是StatefulWidget和StatelessElement的父类
  • RenderObjectElement, 是有渲染功能的Element的父类,对应的widget是RenderObjectWidget
  • RenderObjectToWidgetElement,使用runApp()方法创建了第一个Element 和 RenderObject
1.20.1、ComponentElement的performRebuild()
void performRebuild() {
    Widget built;
    try {
      built = build();//1
    } 
    ...
    try {
      _child = updateChild(_child, built, slot);//2
    } 
    ...
  }

  1. 执行element的build();,以StatefulElement的build方法为例:Widget build() => state.build(this);。 就是执行了我们复写的StatefulWidget的state的build方法啦~,这个就是回调到你自己的写的widget的build方法
  • 执行build方法build出来的是啥呢? 当然就是这个StatefulWidget的子Widget了。Element就是在这个地方决定如何更新

参数说明:参数child 是上一次Element挂载的child Element, newWidget 是刚刚build出来的,Slot这个一系列类型变量的含义:由父节点设置的信息,用于定义子节点在父节点中的位置

 Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
   //1,----- 如果' newWidget '是空的,而' child '不是空的,那么我们需要///删除它,因为它不再有一个配置
 
   if (newWidget == null) {//如果刚build出来的widget等于null,说明这个控件被删除了,child Element可以被删除了。
      if (child != null) //就是原先该element树位置存在东西,上面widget树这个位置没东西了,通知element将东西删除
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    
    
    //newWidget 和child 两者都不为空,那么我们需要更新“子”的配置到///是由“newWidget”给出的新配置
    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;
      }());
      
      //2.-----如果当前位置的上一个widget与新构建的widget是同一个widget,可以看作没加 key的情况
      if (hasSameSuperclass && child.widget == newWidget) {
      	//2.1---- 位置一样不,不一样就更新下
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
          
        newChild = child;
      } 
      //3.-----看下Widget是否可以update,Widget.canUpdate的逻辑是判断key值和运行时类型是否相等。如果满足条件的话,就更新,并返回。
      else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        //3.1-------位置一样不,不一样就更新下
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
          
        child.update(newWidget);//调用该方法看你的控件是哪个比如:上一个是StatefulElement则会跑到StatefulElement的update方法中
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      }
      //4.-----
      else {
      	//将给定元素移动到非活动元素列表中,并将其分离
        deactivateChild(child);
        assert(child._parent == null);
        //为给定的小部件创建一个元素,并将其添加为该小部件的子元素
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
    //5--------.newWidget 新加的widget不为null child为null,
      newChild = inflateWidget(newWidget, newSlot);
    }

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

    return newChild;
  }
  • newWidget == null —— 说明子节点对应的 Widget 已被移除,直接 remove child element (如有);

  • child == null —— 说明 newWidget 是新插入的,创建子节点 (inflateWidget);

  • child!=null

    —— 此时,分为 3 种情况:

    • 若 child.widget == newWidget,说明 child.widget 前后没有变化,若 child.slot != newSlot 表明子节点在兄弟结点间移动了位置,通过updateSlotForChild修改 child.slot 即可;
    • 通过Widget.canUpdate判断是否可以用 newWidget 修改 child element,若可以,则调用update方法;
    • 否则先将 child element 移除,并通 newWidget 创建新的 element 子节点。

    这里的解释可以搜索本文中:(我的例子:)这几个文字,查看示例,看看对应下面解释的5种的哪几种

    看上面那段代码标记点的含义:

    1.如果刚build出来的widget等于null,说明这个控件被删除了,child Element可以被删除了。(如果' newWidget '是空的,而' child '不是空的,那么我们需要///删除它,因为它不再有一个配置。)

    2.如果child的widget和新build出来的一样(Widget复用了),就看下位置一样不,不一样就更新下,一样就直接return了。Element还是旧的Element ,**

    3.看下Widget是否可以update,Widget.canUpdate的逻辑是判断key值和运行时类型是否相等。如果满足条件的话,就更新,并返回。(如果' newWidget '可以被赋予///给现有的子组件(由[Widget.canUpdate]确定),那么它是这样的///给定的。否则,旧的子节点需要被销毁,并为新配置创建一个新的子节点)

    (对应 我的例子:eg1中的 方法四) 没加任何key

    中间商的差价哪来的呢?只要新build出来的Widget和上一次的类型和Key值相同,Element就会被复用!由此也就保证了虽然Widget在不停的新建,但只要不发生大的变化,那Element是相对稳定的,也就保证了RenderObject是稳定的

    4./将给定元素移动到非活动元素列表中,并将其分离,为给定的小部件创建一个元素,并将其添加为该小部件的子元素 (对应 我的例子:eg1中的 方法二,方法三,不过 方法二有一个globeKey导致可以复用,而方法四使用的UniqueKey()导致发生重建)

    5.为给定的小部件创建一个元素,并将其添加为该小部件的子元素(如果' child '是空的,而' newWidget '不是空的,那么我们需要为它创建一个新的///子元素,用' newWidget '配置)

    **1.20.1.1、**下面主要是关注inflateWidget()方法

    inflateWidget的解释:首先会尝试通过GlobalKey去查找可复用的Element,复用失败就调用Widget的方法创建新的Element,然后调用mount方法,将自己挂载到父Element上去,mount之前我们也讲过,会在这个方法里创建新的RenderObject。(注意:只有RenderObjectWidget 类型的widget 才能通过RenderObjectElement 去创建RenderObject树, -----其他的类似StateFullWidger这个是因为很多的Widget 它其实更多的是一个盒子的作用并不是一个可渲染的东西

    Element inflateWidget(Widget newWidget, Object? newSlot) {
      assert(newWidget != null);
      final Key? key = newWidget.key;
      if (key is GlobalKey) {//首先会尝试通过GlobalKey去查找可复用的Element
        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!;
        }
      }
      //复用失败就调用Widget的方法创建新的Element
      final Element newChild = newWidget.createElement();
      assert(() {
        _debugCheckForCycles(newChild);
        return true;
      }());
      //然后调用mount方法,将自己挂载到父Element上去,mount之前我们也讲过,会在这个方法里创建新的RenderObject。
      newChild.mount(this, newSlot);
      assert(newChild._lifecycleState == _ElementLifecycle.active);
      return newChild;
    }
    

1.20.2、RenderObjectElement的performRebuild(),
(这种类型的widget就会创建RenderObject,哪些StatefulWidget 之类的也相当于RenderObject中的配置)

比如padding控件就是:RenderObjectElement

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

与ComponentElement的不同之处在于,没有去build,而是调用了updateRenderObject方法更新RenderObject。

看一下padding控件的该方法

@override
void updateRenderObject(BuildContext context, RenderPadding renderObject) {
  renderObject
    ..padding = padding
    ..textDirection = Directionality.maybeOf(context);
}

一些看起来比较熟悉的赋值操作,像不像Android的view呀? 要不怎么说RenderObject实际相当于Android里的View呢。

到这里你基本就明白了Element是如何在中间应对Widget的多变,保障RenderObject的相对不变了吧~

相当于赋值创建了一个RenderPadding的对象

我的例子:

eg1:Padding 里面包一个下面总共三种方式:
  • 1.padding加上key:UniqueKey(),自身啥也不加
  • 2.StatefulColorfulTile(key:GlobalKey(),color: Color(0xff991364))只在 自身加上globalKey()
  • 3.StatefulColorfulTile(key:UniqueKey(),color: Color(0xff991364)), 只在自身加上key:UniqueKey()
  • 4:去掉所有key,只保留 StatefulColorfulTile(color: Color(0xff991364)),
// 无状态的外部包一层
Padding(
   key:UniqueKey(),//1
  //三种测试方式 一种是给padding设置key,一种是给它内部设置GlobalKey,,一种是给它内部设置UniqueKey(无效)
  padding: const EdgeInsets.all(8.0),
  child: StatefulColorfulTile(color: Color(0xff991364)),//1
  // child: StatefulColorfulTile(key:GlobalKey(),color: Color(0xff991364)),//2
  // child: StatefulColorfulTile(key:UniqueKey(),color: Color(0xff991364)),//UniqueKey,将会导致创建一个新元素,并初始化一个新状态//3
),
Padding(
  key:UniqueKey(),
  padding: const EdgeInsets.all(8.0),
  child: StatefulColorfulTile(color: Color(0xFF0DEA5E)),
  // child: StatefulColorfulTile(key: GlobalKey(),color: Color(0xFF0DEA5E)),
  // child: StatefulColorfulTile(key: UniqueKey(),color: Color(0xFF0DEA5E)),//UniqueKey
),

测试结果如下:

方法一、结果日志:padding加上key:UniqueKey(), 结果:现象颜色发生交换改变

parent build 更新

走的是Element 的updateChild 方法的第二个条件,总共为五个
 if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      }

方法2、结果日志:加个globeKey 结果:现象颜色发生交换改变

I/flutter (24202): parent build 更新
I/flutter (24202): child deactive
I/flutter (24202): child deactive
I/flutter (24202): child build 
I/flutter (24202): child build 

方法3、结果日志:只在自身加个key:UniqueKey (这种相当于整个子树重建)结果:现象颜色随机变化,因为重建了initState

I/flutter (24202): parent build 更新
I/flutter (24202): child deactive
I/flutter (24202): StatefulColorfulTile 随机值createState
I/flutter (24202): child initState 随机值1
I/flutter (24202): child didChangeDependencies
I/flutter (24202): child build 
I/flutter (24202): child deactive
I/flutter (24202): StatefulColorfulTile 随机值createState
I/flutter (24202): child initState 随机值4
I/flutter (24202): child didChangeDependencies
I/flutter (24202): child build 
I/flutter (24202): child dispose
I/flutter (24202): child dispose

方法4:去掉任何key,只保留 StatefulColorfulTile(color: Color(0xff991364)),

I/flutter (24202): parent build 更新
I/flutter (24202): child didUpdateWidget
I/flutter (24202): child build 
I/flutter (24202): child didUpdateWidget
I/flutter (24202): child build 

eg2:包一个下面总共三种方式:

方法一、直接一层使用valueKey 或者 UniqueKey()

StatefulColorfulTile(key: ValueKey("1"), color:Color(0xff991364)),
StatefulColorfulTile(key: ValueKey("2"), color:Color(0xFF0DEA5E)),

或者:
  StatefulColorfulTile(key: UniqueKey(), color:Color(0xff991364)),
  StatefulColorfulTile(key: UniqueKey(), color:Color(0xFF0DEA5E)),
结果:parent build 更新

走的是Element 的updateChild 方法的第二个条件,总共为五个
 if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      }

方法二、一个StatefulColorfulTile加了key 一个ColorLessTileState()

StatefulColorfulTile(key:UniqueKey(), color:Color(0xff991364)),
ColorLessTileState(),
结果:

I/flutter ( 7424): parent build 更新
I/flutter ( 7424): child less build 

走的是Element 的updateChild 方法的第二个条件,总共为五个
 if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      }

方法三、一个StatefulColorfulTile 不加key 一个ColorLessTileState()

  StatefulColorfulTile( color:Color(0xff991364)),
   ColorLessTileState(),


结果:
 parent build 更新
I/flutter ( 7424): child deactive
I/flutter ( 7424): StatefulColorfulTile 随机值createState
I/flutter ( 7424): child initState 随机值9
I/flutter ( 7424): child didChangeDependencies
I/flutter ( 7424): child build 
I/flutter ( 7424): child less build 
I/flutter ( 7424): child dispose

走的是Element 的updateChild 方法的最后一个else

else {
  newChild = inflateWidget(newWidget, newSlot);
}

方法四、两个ColorLessTileState()

ColorLessTileState(),
ColorLessTileState(),
I/flutter ( 6232): parent build 更新
I/flutter ( 6232): child less build 
I/flutter ( 6232): child less build 


走的是Element 的updateChild 方法的第二个条件,总共为五个
 if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      }

去掉所有key为什么StatelessWidget显示效果是改变,StatefulWidget显示没改变的样子

StatelessWidget

在第一种使用 StatelessWidget 的实现中,当 Flutter 渲染这些 Widgets 时,Row Widget 为它的子 Widget 提供了一组有序的插槽。对于每一个 Widget,Flutter 都会构建一个对应的 Element。构建的这个 Element Tree 相当简单,仅保存有关每个 Widget 类型的信息以及对子Widget 的引用。你可以将这个 Element Tree 当做就像你的 Flutter App 的骨架。它展示了 App 的结构,但其他信息需要通过引用原始Widget来查找。

当我们交换行中的两个色块时,Flutter 遍历 Widget 树,看看骨架结构是否相同。它从 Row Widget 开始,然后移动到它的子 Widget,Element 树检查 Widget 是否与旧 Widget 是相同类型和 Key。 如果都相同的话,它会更新对新 widget 的引用。在我们这里,Widget 没有设置 Key,所以Flutter只是检查类型。它对第二个孩子做同样的事情。所以 Element 树将根据 Widget 树进行对应的更新

当 Element Tree 更新完成后,Flutter 将根据 Element Tree 构建一个 Render Object Tree,最终开始渲染流程。

StatefulWidget

当使用 StatefulWidget 实现时,控件树的结构也是类似的,只是现在 color 信息没有存储控件自身了,而是在外部的 State 对象中。

现在,我们点击按钮,交换控件的次序,Flutter 将遍历 Element 树,检查 Widget 树中 Row 控件并且更新 Element 树中的引用,然后第一个 Tile 控件检查它对应的控件是否是相同类型,它发现对方是相同的类型; 然后第二个 Tile 控件做相同的事情,最终就导致 Flutter 认为这两个控件都没有发生改变。Flutter 使用 Element 树和它对应的控件的 State 去确定要在设备上显示的内容, 所以 Element 树没有改变,显示的内容也就不会改变

添加了 Key 之后的结构:

当现在执行 swap 时, Element 数中 StatafulWidget 控件除了比较类型外,还会比较 key 是否相等:

只有类型和key 都匹配时,才算找到对应的 Widget。于是在 Widget Tree 发生交换后,Element Tree 中子控件和原始控件对应关系就被打乱了,所以 Flutter 会重建 Element Tree,直到控件们正确对应上。

所以,现在 Element 树正确更新了,最终就会显示交换后的色块。

附上flutter生命周期图:

image.png

参考:

juejin.cn/post/684490…

medium.com/flutter/key…

www.lmlphp.com/user/22833/…

juejin.cn/post/684490…

zxfcumtcs.github.io/2020/05/01/…