Flutter源码分析之setState分析

setState方法算是flutter使用最频繁的方法了,每次页面数据有改变,都需要调用这个方法,去触发页面的刷新,展示最新的UI效果,接下来从源码角度解读下setState后具体发生了什么

系统源码部分,会做截取,仅保留跟主题有关的部分,开始吧

void setState(VoidCallback fn) {
    // 省略了一大堆的判断代码
    final Object? result = fn() as dynamic;
    _element!.markNeedsBuild();
  }

上面可以看到,回调方法VoidCallback fn是马上会被同步执行,然后调用这个widget对应的element的markNeedsBuild方法

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

就是把这个element标记为dirty,如果已经标记过,则忽略,说明连续调用两次setState方法,第二次其实是多余的,然后是调用owner的scheduleBuildFor方法

这里的owner,是BuildOwner,先记住全局只有一个BuildOwner实例,它是在启动的时候创建的,这里先不展开说明,我们先记住全局就一个owner就好


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

上面的源码,其实写法上有点不合理,我改下,效果一样,但是更合理些

_dirtyElements.add(element);
element._inDirtyList = true;
 if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
  }

首先,先把当前的element加到一个_dirtyElements的数组里面,_scheduledFlushDirtyElements用于判断有没有调用过刷新dirtyElement,一般会是false,然后调用onBuildScheduled!()方法,这个方法,其实是一个回调方法

VoidCallback? onBuildScheduled;

真正的方法是这个

void _handleBuildScheduled() {
    ensureVisualUpdate();
  }
void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

判断当前的状态,然后触发scheduleFrame,计划刷新下一帧

void scheduleFrame() {
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
  }

这个方法,其实就是让系统触发下一帧的刷新,系统刷新有固定的频率,一般是一秒60帧,内部已经是底层的方法了

// window内方法
void scheduleFrame() => platformDispatcher.scheduleFrame();

void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

调用到engine层的方法了,告知系统触发下一帧的回调,然后会收到系统下一帧刷新的回调,接收方法在这里

void _handleDrawFrame() {
    handleDrawFrame();
  }

这个方法被调用,说明已经到下一帧的刷新时间了

void handleDrawFrame() {
    _frameTimelineTask?.finish(); // 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>.of(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      _frameTimelineTask?.finish(); // end the Frame
      }());
      _currentFrameTimeStamp = null;
    }
  }

这个方法有点长,widget的回调其实是在_persistentCallbacks里面,继续走到_invokeFrameCallback方法里.

void _invokeFrameCallback(FrameCallback callback, Duration timeStamp) {
    try {
      callback(timeStamp);
    } catch (exception, exceptionStack) {
    }
  }

这里的callback方法,其实下面这个方法:

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

继续调用drawFrame方法


void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally { 
    }
  }

又回到了调用buildOwner的方法了

void buildScope(Element context) {
    try {
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        final Element element = _dirtyElements[index];
        try {
          element.rebuild();
        } catch (e, stack) {
        }
    } finally {
      for (final Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
    }
  }

先把_dirtyElements做下排序,越靠近root的靠前,先执行刷新,然后调用每个element的rebuild方法,最后再把_dirtyElements数组清空

void rebuild() {
    performRebuild();
  }

继续往下调用

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

然后调用super的performRebuild方法,然后又回调到StatefulElement的build方法

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

最终触发的地方,就是在这里了

总结

setState其实就是告诉系统,在下一帧刷新的时候,需要更新当前widget,整个过程,是一个异步的行为,所以下面的三个写法,效果上是一样的

// 写法一
_counter++;        
setState(() {});   

// 写法二
setState(() {  
  _counter++;  
});            

// 写法三
setState(() {}); 
_counter++;   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值