前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter源码:setState分析

flutter源码:setState分析

作者头像
韦东锏
发布2022-11-07 13:10:06
4060
发布2022-11-07 13:10:06
举报
文章被收录于专栏:Android码农Android码农

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

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

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

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

代码语言:javascript
复制
void markNeedsBuild() {
    if (dirty)
      return;
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

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

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

代码语言:javascript
复制
void scheduleBuildFor(Element element) {
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }

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

代码语言:javascript
复制
_dirtyElements.add(element);
element._inDirtyList = true;
 if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
  }

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

代码语言:javascript
复制
VoidCallback? onBuildScheduled;

真正的方法是这个

代码语言:javascript
复制
void _handleBuildScheduled() {
    ensureVisualUpdate();
  }
代码语言:javascript
复制
void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

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

代码语言:javascript
复制
void scheduleFrame() {
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
  }

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

代码语言:javascript
复制
// window内方法
void scheduleFrame() => platformDispatcher.scheduleFrame();

void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

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

代码语言:javascript
复制
void _handleDrawFrame() {
    handleDrawFrame();
  }

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

代码语言:javascript
复制
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方法里

代码语言:javascript
复制
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp) {
    try {
      callback(timeStamp);
    } catch (exception, exceptionStack) {
    }
  }

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

代码语言:javascript
复制
void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _scheduleMouseTrackerUpdate();
  }

继续调用drawFrame方法

代码语言:javascript
复制
void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally { 
    }
  }

又回到了调用buildOwner的方法了

代码语言:javascript
复制
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的靠前,先执行刷新,然后调用每个elementrebuild方法,最后再把_dirtyElements数组清空

代码语言:javascript
复制
void rebuild() {
    performRebuild();
  }

继续往下调用

代码语言:javascript
复制
void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

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

代码语言:javascript
复制
Widget build() => state.build(this);

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

总结

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

代码语言:javascript
复制
// 写法一
_counter++;        
setState(() {});   

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

// 写法三
setState(() {}); 
_counter++;      
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档