Flutter的setState(状态刷新)

Flutter有两个常用的状态类:

  • StatelessWidget:无状态类,没有状态更新,界面一经创建无法更改。
  • StatefulWidget:有状态类,当状态有改变时,调用setState(),方法会触发StatefulWidget的UI更新。
    那么setState是如何刷新的呢?我们来看一下setState()的内部逻辑:


    流程图

    在State类中定义了setState方法:

@protected
void setState(VoidCallback fn) {
    _element.markNeedsBuild();
}

标记为dirty,执行的markNeedsBuild,定义在Element类中:

void markNeedsBuild() {
    if (!_active)
        return;
    if (dirty)
        return;
    _dirty = true;// 标记为dirty(脏的)
    owner.scheduleBuildFor(this);
}

当前Element节点被标记为dirty,同时调用owner的scheduleBuildFor方法:
将element元素添加到全局的“脏”链表里。

void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
        _dirtyElementsNeedsResorting = true;
        return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
        _scheduledFlushDirtyElements = true;
        onBuildScheduled();// 回调
    }
    _dirtyElements.add(element);// 添加到“脏”链表里
    element._inDirtyList = true;
}

BuildOwner用来管理哪些需要更新的Widget。这个owner最开始被初始化的地方在WidgetsBinding的initInstances方法中,随后初始化了onBuildScheduled方法,对应执行的是_handleBuildScheduled,定义在WidgetsBinding类中:

void _handleBuildScheduled() {
    ensureVisualUpdate();
}

ensureVisualUpdate 方法定义在SchedulerBinding类中:

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

在提交下一帧绘制的时候会调用到scheduleFrame方法,提交给引擎绘制,看看scheduleFrame方法,也定义在SchedulerBinding类中:

void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();//native 'Window_scheduleFrame'
    _hasScheduledFrame = true;
}

@protected
void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
}

提交给引擎绘制之后,会收到onDrawFrame的回调,最终执行到_handleDrawFrame方法中,对应的是handleDrawFrame方法,定义在SchedulerBinding类中:

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
      _currentFrameTimeStamp = null;
    }
}

在RendererBinding的initInstances方法中添加了一个回调到这个List中,对应的是RenderBinding的drawFrame方法,对应的节点进行绘制渲染操作。

WidgetsBinding中的drawFrame方法:

@override
void drawFrame() {
    TimingsCallback firstFrameCallback;
    if (_needToReportFirstFrame) {
      firstFrameCallback = (List<FrameTiming> timings) {
        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 [Window.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();
      buildOwner.finalizeTree();
    } finally {
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && sendFramesToEngine) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
    if (firstFrameCallback != null && !sendFramesToEngine) {
      SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
    }
}

看看这里的buildScope方法,定义在BuildOwner方法中。在上面 scheduleBuildFor 方法介绍中有提到:"scheduleBuildFor 是把一个 element 添加到 _dirtyElements 链表,以便当[WidgetsBinding.drawFrame]中调用 buildScope 的时候能够重构 element。onBuildScheduled()是一个 BuildOwner 的回调"。在 drawFrame 中调用 buildOwner.buildScope(renderViewElement)更新 elements。

void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
        return;
    Timeline.startSync('Build', arguments: timelineWhitelistArguments);
    _dirtyElements.sort(Element._sort);
    _dirtyElementsNeedsResorting = false;
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
        try {
            //while 循环进行元素重构
            _dirtyElements[index].rebuild();
        } catch (e, stack) {
        }finally {
            for (final Element element in _dirtyElements) {
                element._inDirtyList = false;
            }
            _dirtyElements.clear();
            _scheduledFlushDirtyElements = false;
            _dirtyElementsNeedsResorting = null;
            Timeline.finishSync();
        }
    }
}

_dirtyElements列表在遍历的过程中执行rebuild方法,此时将所有标记为dirty的Element节点依次执行rebuild,preformRebuild,build,updateChild,update方法,执行界面更新。完成build,update操作完成之后,后续会将需要绘制的RenderObject添加到需要layout的列表中,等待绘制渲染。所有绘制完成之后将_dirtyElments列表清空,_inDirtyList标记位置为false。

提交给引擎绘制渲染
看看super.drawFrame(),这里就执行到了RendererBinding类中,定义如下:

void drawFrame() {
    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;
    }
}

这里就是将最终需要绘制渲染的画面提交给引擎的地方了,绘制完成之后就在界面显示更新后的视图了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345