Flutter筆記——State.setState發(fā)生了什么(源碼學習)

終于放假啦金闽,不用一直寫業(yè)務代碼了条摸,也終于有點時間可以整理整理筆記啦。
我在這里先給大家拜個早年覆旭,恭祝大家新春快樂退子,吉祥安康啦!

拜年.gif

Flutter系列學習筆記

1 State.setState

見該函數(shù)描述:

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對象的內(nèi)部狀態(tài)發(fā)生了變化寂祥。無論你何時改變一個State對象的內(nèi)部狀態(tài),請在傳遞給setState函數(shù)中做出修改七兜。

在開發(fā)中我們通過控制setState對當前Widget做出修改壤靶,讓UI元素重繪,過程見下面序列圖

setState序列圖.png

注釋部分不清楚見下文:

  1. 斷言傳遞函數(shù)不為null惊搏;
    當前state的狀態(tài)判斷贮乳,參見_StateLifecycle枚舉和源碼;
    傳遞的函數(shù)不能是future恬惯;
    最后調(diào)用_element.markNeedsBuild()向拆,下一幀時當前State中Element需要重新build。
  2. 判斷_debugLifecycleState的狀態(tài)酪耳,參考_ElementLifecycle枚舉和源碼浓恳;
    當前Element的owner和active斷言刹缝,active在deactivate函數(shù)被置為false,在mountedactive被置為true颈将,所以調(diào)用setStae的時機需要注意梢夯;
    debug模式下的斷言;
    如果當前Element._dirty已經(jīng)為true的話返回;
    調(diào)用owner.scheduleBuildFor(this)函數(shù)晴圾。
  3. 將element加入到BuildOwner的dirty列表中颂砸,WidgetBinding在下一幀時會通過drawFrame繪制該element。
  4. element的_dirty標志位置為true死姚,加入到dirty列表中人乓,并且已經(jīng)mount、active且未deactivate都毒。dispose色罚。下一幀該Element會通過RenderObject在其區(qū)域重繪。

2 源碼學習

  1. State.setState(VoidCallback fn):
      @protected
      void setState(VoidCallback fn) {
        //斷言fn不為空
        assert(fn != null);
        assert(() {
          //當前State的狀態(tài)账劲,不能等于_StateLifecycle.defunct戳护,
          //該狀態(tài)時,dispose函數(shù)已經(jīng)調(diào)用
          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即使創(chuàng)建了瀑焦,也必須調(diào)用了mounted函數(shù)添加到樹上
          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轉(zhuǎn)為dynamic動態(tài)類型
        final dynamic result = fn() as dynamic;
        assert(() {
          if (result is Future) {
            //如果result是一個Future腌且,拋出異常。因為setState函數(shù)在下一幀就會重繪
            //Future函數(shù)是異步的蝠猬,不能確定具體重繪時間
            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;
        }());
        //調(diào)用State中的_elemnt.markNeedsBuild()函數(shù)
        _element.markNeedsBuild();
      }
    
  2. Element.markNeedsBuld():
      void markNeedsBuild() {
        //斷言Element的當前狀態(tài)不能等于defunct
        assert(_debugLifecycleState != _ElementLifecycle.defunct);
        //未處于active狀態(tài)切蟋,
        if (!_active)
          return;
        //owner等于空斷言異常
        assert(owner != null);
        //當前狀態(tài)必須等于_ElementLifecycle.active
        assert(_debugLifecycleState == _ElementLifecycle.active);
        assert(() {
          //此Widget樹是否出于構建階段
          if (owner._debugBuilding) {
            //當前構建的目標不能等于null
            assert(owner._debugCurrentBuildTarget != null);
            //調(diào)用BuildOwner.lockState函數(shù)_debugStateLockLevel會增加统捶,也就是
            //當前BuildOwner已經(jīng)鎖定State
            assert(owner._debugStateLocked);
            //判斷當前構建的目標是否在構建域中
            if (_debugIsInScope(owner._debugCurrentBuildTarget))
              return true;
            //_debugAllowIgnoredCallsToMarkNeedsBuild該標志位位false時榆芦,
            //在State的initState、didUpdateWidget和build函數(shù)中調(diào)用setState函數(shù)都會報錯喘鸟。
            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) {
            //狀態(tài)已經(jīng)鎖定匆绣,斷言會報錯
            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已經(jīng)被標志位dirty,返回
        if (dirty)
          return;
        //當前Element的_dirty設置為true
        _dirty = true;
        //調(diào)用owner.scheduleBuildFor(Element)函數(shù)
        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狀態(tài)
            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已經(jīng)處于dirty臟列表中
          assert(() {
            if (debugPrintScheduleBuildForStacks)
              debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
            //_debugIsInBuildScope該值等于true時什黑,才可以調(diào)用scheduleBuildFor函數(shù)
            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 小結(jié)

'State.setState()'函數(shù)將當前State攜帶的Element對象加入到BuildOwner對象的dirtyList集合中崎淳,等待下幀繪制時重繪。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愕把,一起剝皮案震驚了整個濱河市拣凹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恨豁,老刑警劉巖嚣镜,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異橘蜜,居然都是意外死亡菊匿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跌捆,“玉大人徽职,你說我怎么就攤上這事∨搴瘢” “怎么了姆钉?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長可款。 經(jīng)常有香客問我育韩,道長,這世上最難降的妖魔是什么闺鲸? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任筋讨,我火速辦了婚禮,結(jié)果婚禮上摸恍,老公的妹妹穿的比我還像新娘悉罕。我一直安慰自己,他們只是感情好立镶,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布壁袄。 她就那樣靜靜地躺著,像睡著了一般媚媒。 火紅的嫁衣襯著肌膚如雪嗜逻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天缭召,我揣著相機與錄音栈顷,去河邊找鬼。 笑死嵌巷,一個胖子當著我的面吹牛萄凤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搪哪,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼靡努,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晓折?” 一聲冷哼從身側(cè)響起惑朦,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漓概,沒想到半個月后漾月,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡垛耳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年栅屏,在試婚紗的時候發(fā)現(xiàn)自己被綠了飘千。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡栈雳,死狀恐怖护奈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哥纫,我是刑警寧澤霉旗,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站蛀骇,受9級特大地震影響厌秒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擅憔,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一鸵闪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暑诸,春花似錦蚌讼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至西采,卻和暖如春凰萨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背械馆。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工胖眷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狱杰。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓瘦材,卻偏偏與公主長得像厅须,于是被迫代替她去往敵國和親仿畸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容