Flutter笔记——State.setState发生了什么(源码学习)

终于放假啦,不用一直写业务代码了,也终于有点时间可以整理整理笔记啦。
我在这里先给大家拜个早年,恭祝大家新春快乐,吉祥安康啦!

拜年.gif

Flutter系列学习笔记

1 State.setState

见该函数描述:

Notify the framework that the internal state of this object has changed.
Whenever you change the internal state of a State object, make the change in a function that you pass to setState.
通知Framework层:该State对象的内部状态发生了变化。无论你何时改变一个State对象的内部状态,请在传递给setState函数中做出修改。

在开发中我们通过控制setState对当前Widget做出修改,让UI元素重绘,过程见下面序列图

setState序列图.png

注释部分不清楚见下文:

  1. 断言传递函数不为null;
    当前state的状态判断,参见_StateLifecycle枚举和源码;
    传递的函数不能是future;
    最后调用_element.markNeedsBuild(),下一帧时当前State中Element需要重新build。
  2. 判断_debugLifecycleState的状态,参考_ElementLifecycle枚举和源码;
    当前Element的owner和active断言,active在deactivate函数被置为false,在mountedactive被置为true,所以调用setStae的时机需要注意;
    debug模式下的断言;
    如果当前Element._dirty已经为true的话返回;
    调用owner.scheduleBuildFor(this)函数。
  3. 将element加入到BuildOwner的dirty列表中,WidgetBinding在下一帧时会通过drawFrame绘制该element。
  4. element的_dirty标志位置为true,加入到dirty列表中,并且已经mount、active且未deactivate。dispose。下一帧该Element会通过RenderObject在其区域重绘。

2 源码学习

  1. State.setState(VoidCallback fn):
      @protected
      void setState(VoidCallback fn) {
        //断言fn不为空
        assert(fn != null);
        assert(() {
          //当前State的状态,不能等于_StateLifecycle.defunct,
          //该状态时,dispose函数已经调用
          if (_debugLifecycleState == _StateLifecycle.defunct) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('setState() called after dispose(): $this'),
              ErrorDescription(
                'This error happens if you call setState() on a State object for a widget that '
                'no longer appears in the widget tree (e.g., whose parent widget no longer '
                'includes the widget in its build). This error can occur when code calls '
                'setState() from a timer or an animation callback.'
              ),
              ErrorHint(
                'The preferred solution is '
                'to cancel the timer or stop listening to the animation in the dispose() '
                'callback. Another solution is to check the "mounted" property of this '
                'object before calling setState() to ensure the object is still in the '
                'tree.'
              ),
              ErrorHint(
                'This error might indicate a memory leak if setState() is being called '
                'because another object is retaining a reference to this State object '
                'after it has been removed from the tree. To avoid memory leaks, '
                'consider breaking the reference to this object during dispose().'
              ),
            ]);
          }
          //State即使创建了,也必须调用了mounted函数添加到树上
          if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('setState() called in constructor: $this'),
              ErrorHint(
                'This happens when you call setState() on a State object for a widget that '
                'hasn\'t been inserted into the widget tree yet. It is not necessary to call '
                'setState() in the constructor, since the state is already assumed to be dirty '
                'when it is initially created.'
              ),
            ]);
          }
          return true;
        }());
        //将fn转为dynamic动态类型
        final dynamic result = fn() as dynamic;
        assert(() {
          if (result is Future) {
            //如果result是一个Future,抛出异常。因为setState函数在下一帧就会重绘
            //Future函数是异步的,不能确定具体重绘时间
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('setState() callback argument returned a Future.'),
              ErrorDescription(
                'The setState() method on $this was called with a closure or method that '
                'returned a Future. Maybe it is marked as "async".'
              ),
              ErrorHint(
                'Instead of performing asynchronous work inside a call to setState(), first '
                'execute the work (without updating the widget state), and then synchronously '
               'update the state inside a call to setState().'
              ),
            ]);
          }
          // We ignore other types of return values so that you can do things like:
          //   setState(() => x = 3);
          return true;
        }());
        //调用State中的_elemnt.markNeedsBuild()函数
        _element.markNeedsBuild();
      }
    
  2. Element.markNeedsBuld():
      void markNeedsBuild() {
        //断言Element的当前状态不能等于defunct
        assert(_debugLifecycleState != _ElementLifecycle.defunct);
        //未处于active状态,
        if (!_active)
          return;
        //owner等于空断言异常
        assert(owner != null);
        //当前状态必须等于_ElementLifecycle.active
        assert(_debugLifecycleState == _ElementLifecycle.active);
        assert(() {
          //此Widget树是否出于构建阶段
          if (owner._debugBuilding) {
            //当前构建的目标不能等于null
            assert(owner._debugCurrentBuildTarget != null);
            //调用BuildOwner.lockState函数_debugStateLockLevel会增加,也就是
            //当前BuildOwner已经锁定State
            assert(owner._debugStateLocked);
            //判断当前构建的目标是否在构建域中
            if (_debugIsInScope(owner._debugCurrentBuildTarget))
              return true;
            //_debugAllowIgnoredCallsToMarkNeedsBuild该标志位位false时,
            //在State的initState、didUpdateWidget和build函数中调用setState函数都会报错。
            if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
              final List<DiagnosticsNode> information = <DiagnosticsNode>[
                ErrorSummary('setState() or markNeedsBuild() called during build.'),
                ErrorDescription(
                  'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
                  'is already in the process of building widgets.  A widget can be marked as '
                  'needing to be built during the build phase only if one of its ancestors '
                  'is currently building. This exception is allowed because the framework '
                  'builds parent widgets before children, which means a dirty descendant '
                  'will always be built. Otherwise, the framework might not visit this '
                  'widget during this build phase.'
                ),
                describeElement(
                  'The widget on which setState() or markNeedsBuild() was called was',
                ),
              ];
              if (owner._debugCurrentBuildTarget != null)
                information.add(owner._debugCurrentBuildTarget.describeWidget('The widget which was currently being built when the offending call was made was'));
              throw FlutterError.fromParts(information);
            }
            assert(dirty); 
          } else if (owner._debugStateLocked) {
            //状态已经锁定,断言会报错
            assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
              ErrorDescription(
                'This ${widget.runtimeType} widget cannot be marked as needing to build '
                'because the framework is locked.'
              ),
              describeElement('The widget on which setState() or markNeedsBuild() was called was'),
            ]);
          }
          return true;
        }());
        //如果该Element已经被标志位dirty,返回
        if (dirty)
          return;
        //当前Element的_dirty设置为true
        _dirty = true;
        //调用owner.scheduleBuildFor(Element)函数
        owner.scheduleBuildFor(this);
      }
    
  3. BuildOwner.scheduleBuildFor(Element):
      void scheduleBuildFor(Element element) {
        //Element不能为空
        assert(element != null);
        //element的BuildOwner对象必须等于当前对象
        assert(element.owner == this);
        assert(() {
          if (debugPrintScheduleBuildForStacks)
            debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
          if (!element.dirty) {
            //当前Element不是dirty状态
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'),
              element.describeElement('The method was called for the following element'),
              ErrorDescription(
                'This element is not current marked as dirty. Make sure to set the dirty flag before '
                'calling scheduleBuildFor().'),
              ErrorHint(
                'If you did not attempt to call scheduleBuildFor() yourself, then this probably '
                'indicates a bug in the widgets framework. Please report it:\n'
                '  https://github.com/flutter/flutter/issues/new?template=BUG.md'
              ),
            ]);
          }
          return true;
        }());
        if (element._inDirtyList) {
          //element已经处于dirty脏列表中
          assert(() {
            if (debugPrintScheduleBuildForStacks)
              debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
            //_debugIsInBuildScope该值等于true时,才可以调用scheduleBuildFor函数
            if (!_debugIsInBuildScope) {
              throw FlutterError.fromParts(<DiagnosticsNode>[
                ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'),
                ErrorHint(
                  'The BuildOwner.scheduleBuildFor() method should only be called while the '
                  'buildScope() method is actively rebuilding the widget tree.'
                ),
              ]);
            }
            return true;
          }());
          //需要排序Element树
          _dirtyElementsNeedsResorting = true;
          return;
        }
        //忽略
        if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
          _scheduledFlushDirtyElements = true;
          onBuildScheduled();
        }
        //将element添加到脏元素列表中
        _dirtyElements.add(element);
        //将element的_inDirtyList标记为true
        element._inDirtyList = true;
        assert(() {
          if (debugPrintScheduleBuildForStacks)
            debugPrint('...dirty list is now: $_dirtyElements');
          return true;
        }());
      }
    
    

3 小结

'State.setState()'函数将当前State携带的Element对象加入到BuildOwner对象的dirtyList集合中,等待下帧绘制时重绘。

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

推荐阅读更多精彩内容