Flutter : 关于 setState()

写在前面

当我们对 StatefulWidget 调用 setState() 方法后,就会触发该 Widget 进行 rebuild,以构建我们新的数据。

内容

加入到脏列表

首先看下 setState()方法,里面主要做了两件事,同步地调用回调方法,然后调用 Element 的 markNeedsBuild()方法

/// State

 @protected
  void setState(VoidCallback fn) {
  
    ...
    final Object? result = fn() as dynamic;
    ...
    _element!.markNeedsBuild();
  }

Element 的 markNeedsBuild()方法里会把这个 Element 标记为 dirty ,然后调用 BuildOwner 类型的 owner 的 scheduleBuildFor()方法,把自己加入到一个 List<Element>类型的 _dirtyElements 的列表里

/// Element
  void markNeedsBuild() {
    ...
    if (dirty)
      return;
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

/// BuildOwner
 void scheduleBuildFor(Element element) {
    ...
    if (element._inDirtyList) {
      ...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    ...
  }

关于 BuildOwner

Element 里的这个 BuildOwner 类型的 owner ,是用来管理当前 Element 的生命周期的。

Element 的 owner 是在其 mount()方法被调用的时候,从其 parent 那里赋值的。但它上面总会有个头,就是来自 RootRenderObjectElment 的 assignOwner(BuildOwner owner)方法

/// RootRenderObjectElement
abstract class RootRenderObjectElement extends RenderObjectElement {
  ...
  void assignOwner(BuildOwner owner) {
    _owner = owner;
  }
  ...
}

这个方法被调用是在 binding.dart 的 RenderObjectToWidgetAdapter 里的 attachToRenderTree(BuildOwner owner)方法里被调用:

/// RenderObjectToWidgetAdapter
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  ...
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        // 这里
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }
  ...
}

那么再继续看attachToRenderTree(),在这里的时候就会传入第一个 buildOwner。

/// WidgetsBinding
  void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
    if (isBootstrapFrame) {
      SchedulerBinding.instance!.ensureVisualUpdate();
    }
  }
  
  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }

attachToRenderTree()这个方法再继续跟踪其上面的调用的话,就是 runApp()WidgetsFlutterBinding.ensureInitialized()scheduleAttachRootWidget()方法

而这个 BuildOwner 是一个来自 WidgetsBinding 的属性,它在 WidgetsBinding 的初始化实例函数里被创建。

/// WidgetsBinding
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    ...
    _buildOwner = BuildOwner();
    ...
  }
  
  BuildOwner? _buildOwner;
  ...
}

下一帧的回调

那么从上面我们知道 setState()的方法里是跟 BuildOwner 有关,那么是怎么起作用的呢?

ScheduleBinding 里有一个 handleDrawFrame()的方法,这个方法在 engine 产生新的一帧的时候会被调用:

  void handleDrawFrame() {
    ...
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      ...
    }
  }

它主要是触发 persistent 回调和 postFrame 回调,persistent 回调是典型地作为 build, layout,paint 的渲染管道,postFrame 回调则是用来做清理和下一帧的准备工作。

那既然 ScheduleBinding 这里有 handleDrawFrame()方法用来处理监听帧变化的回调,那么这些回调是一开始在哪里注册呢?

答案是在 RenderBinding 的 initInstances()方法里,我们还可以看到回调被触发后,会调用drawFrame()方法 :

/// 注意 RendererBinding 混入了 SchedulerBinding
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    ...
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    ...
  }
  
   void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }
  
   void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    ...
  }
  
  @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;
    }
  }
}

因为 RenderBinding 混入了 SchedulerBinding,所以 RenderBinding 可以拿到 _persistentCallbacks 这个列表,就能把回调加入到里面去。

但这里 RenderBinding 自身的 drawFrame()却没有跟 Widget 构建有关的信息,是一些布局、渲染之类的。

那这里就要跳到 WidgetsBinding 里了,它混入了 RendererBinding ,重写它的 drawFrame(),做了额外的操作。

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  ...
  @override
  void drawFrame() {
    ...
    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
      ...
    }
    ...
  }
...
}

这里我们就可以知道,上面的回调,会触发到 WidgetsBinding 里重写的 drawFrame()方法,就看到了我们的 buildOwner ,调用了它的 buildScope()方法。

/// BuildOwner

 void buildScope(Element context, [ VoidCallback? callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    ...
    Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        ...
        Element? debugPreviousBuildTarget;
        ...
        _dirtyElementsNeedsResorting = false;
        try {
          callback();
        } finally {
          ...
        }
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
          ...
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            index -= 1;
          }
        }
      }
      ...
    } finally {
      for (final Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
      ...
    }
    ...
  }

buildScope()方法里大致就是遍历脏列表,然后调用里面每一个 Element 的 rebuild()方法,并最后变更状态,清空脏列表。

Rebuild

在 BuildOwner 的 buildScope()方法里触发了 Element 的 rebuild()方法,接着是 performRebuild()

/// Element 
 void rebuild() {
    ...
    if (_lifecycleState != _ElementLifecycle.active || !_dirty)
      return;
    ...
    Element? debugPreviousBuildTarget;
    ...
    performRebuild();
    ...
  }

performRebuild()是个抽象方法,依赖于具体的实现。因为我们一开始说的是 StatefulWidget,那就看下 StatefulElement:

/// StatefulElement

  @override
  void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

可以看出调用了 State 里的 didChangeDependencies(),这就是我们 setState() 的时候,会走 didChangeDependencies()的原因。另外我们还看到它调用了 super.performRebuild(),因为 StatefulElement 继承自 ComponentElement。

在 ComponentElement 里的 performRebuild()方法里:

/// ComponentElement
 @override
  void performRebuild() {
    ...
    Widget? built;
    try {
      ...
      built = build();
      ...
    } catch (e, stack) {
      ...
    } finally {
      _dirty = false;
      ...
    }
    try {
      _child = updateChild(_child, built, slot);
      ...
    } catch (e, stack) {
      ...
      _child = updateChild(null, built, slot);
    }
    ...
  }

调用 build()方法,拿到新的 Widget,然后调用 updateChild()方法来更新。

  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    // 如果 newWidget 为 null,
    // child 也为 null,就直接返回 null,
    // child 不为 null ,就 deactivate child  后再返回 null
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    
    // 如果 newWidget 不为 null
    // child 不为 null 
    if (child != null) {
      bool hasSameSuperclass = true;
      ...
      if (hasSameSuperclass && child.widget == newWidget) {
        // child.widget == newWidget,只要替换就行
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } 
      // 如果 canUpdate() 为真,说明只要更新配置信息即可
      else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        ...
        newChild = child;
      } else {
        // 说明前后的 Widget 是不一样的,那么就 deactivate child ,挂载新的
        deactivateChild(child);
        ...
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      // child 为 null,说明这里是新增了 Widget ,就直接创建新的 Element,并添加进给定的 slot 里。
      newChild = inflateWidget(newWidget, newSlot);
    }
    ...

    return newChild;
  }
  
    Element inflateWidget(Widget newWidget, Object? newSlot) {
    ...
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      final Element? newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        ...
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        ...
        return updatedChild!;
      }
    }
    final Element newChild = newWidget.createElement();
    ...
    newChild.mount(this, newSlot);
    ...
    return newChild;
  }

这里面有一套官方规则的总结:

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

像官方一开始的那个更新数字的 Demo,就会走到 Element 的 update()方法,它的继承者对其有不同的实现:

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

同样的还是看 StatefulElement 的 update()方法:

  @override
  void update(StatefulWidget newWidget) {
    super.update(newWidget);
    ...
    final StatefulWidget oldWidget = state._widget!;
    _dirty = true;
    state._widget = widget as StatefulWidget;
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
      ...
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    rebuild();
  }

这里会调用到生命周期里的didUpdateWidget(),最后调用了 Element 的 rebuild()方法,又回到了我们前面的流程。但由于这里调用的其实是 child 的update()方法,所以可想而知就是不断地往下调用。

而这也就是说为什么 setState()往往会产生比较大的开销的原因,如果 Widget 的粒度相对来说不够细,拆分的不够好,不能针对性的更新,那么就会导致其下方的子 Widget 都要跟着重建。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值