Flutter原理篇:didChangeDependencies什么時(shí)候被調(diào)用

各位岖是,又到了我們原理篇的時(shí)間了帮毁,基于最近幾篇flutter原理篇的文章,這一期應(yīng)該是寫paint重繪篇的內(nèi)容的豺撑,但是最近在調(diào)試程序的時(shí)候遇到一個(gè)小的問題就是關(guān)于didChangeDependencies 方法什么時(shí)候被調(diào)用的有點(diǎn)搞不清楚聪轿,本著搞不清楚就上網(wǎng)查詢的習(xí)慣灯抛,結(jié)果搜了好多篇文章發(fā)現(xiàn)要么就是內(nèi)容非常簡單說了和沒說一樣夹抗,要么就是寫了一個(gè)Demo得出了一個(gè)沒有細(xì)節(jié)經(jīng)不住拷問的大概的結(jié)論(你也不能說他錯(cuò)漠烧,總覺得還是差了點(diǎn)什么),更有甚者就是千篇一律的拷貝內(nèi)容文章(??我想你應(yīng)該懂我的意思)摆舟,本著考究探索的精神發(fā)現(xiàn)了這些文章以后不驚要大呼一聲:
”恨诱!這樣下去是不對的〔蘧椋“
自己必須要搞清楚寫出來才算對得起大家剂碴,也對得起自己在flutter上面的探索忆矛,于是就有了今天這篇文章,好了廢話不多說了漫拭,讓我們進(jìn)入正題來討論下 《didChangeDependencies方法到底是什么時(shí)候被調(diào)用的》

其實(shí)在普通我們寫簡單例子的時(shí)候我可以一句話說清楚didChangeDependencies方法在什么時(shí)候被調(diào)用审胚,那就是在它的 State 對象對應(yīng)的 ElementcreateElement() 的時(shí)候并且被Mount的時(shí)候就會被調(diào)用菲盾,簡單就是說當(dāng)這個(gè)對應(yīng)的 Element 被重新創(chuàng)建的時(shí)候就會被調(diào)用

你看到這里就會說不對啊,你這個(gè)結(jié)論和我寫的Demo對不上啊临谱,和網(wǎng)上一些結(jié)論也大相徑庭啊,你先別著急先往下看抄课,看看到底是怎么一回事

我先上一個(gè)Demo跟磨,這個(gè)Demo也就是網(wǎng)上面?zhèn)鞯谋容^廣的,我們就以這個(gè)來舉例子說明網(wǎng)上的結(jié)論:“父級結(jié)構(gòu)中的層級發(fā)生變化時(shí)didChangeDependencies被調(diào)用“ 這個(gè)結(jié)論為什么是不完整

import 'package:flutter/material.dart';

class TestDidChangeDependencies2 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TestDidChangeDependencies2State();
}

class TestDidChangeDependencies2State extends State<TestDidChangeDependencies2> {
  bool bDependenciesShouldChange = true;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        body: bDependenciesShouldChange ?
            Container(
              height: 500,
              alignment: Alignment.centerLeft,
              child: C(child: B()),
            )
            : Container(
              height: 500,
              alignment: Alignment.centerLeft,
              child: C(child: SizedBox(width: 20, height: 50, child: B())),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          )
    );
  }

  void _incrementCounter(){
    setState(() {
      bDependenciesShouldChange = !bDependenciesShouldChange;
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("AdidChangeDependencies");
  }
}

class B extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => BState();
}

class BState extends State<B> {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("Test"));
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("BdidChangeDependencies");
  }
}

class C extends StatefulWidget {
  final Widget child;

  C({Key? key, required this.child}) : super(key: key);

  @override
  State<StatefulWidget> createState() => CState();
}

class CState extends State<C> {
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("CdidChangeDependencies");
  }
}

class D extends StatelessWidget{

  final Widget child_D;
  D({required this.child_D});

  @override
  Widget build(BuildContext context) {
    return child_D;
  }
}

這個(gè)是網(wǎng)上流傳的Demo,我把它調(diào)試為比較好可運(yùn)行狀態(tài)充尉,大家可以來觀察下輸出,首先剛運(yùn)行的時(shí)候就會打印如下:

I/flutter (20121): AdidChangeDependencies
I/flutter (20121): CdidChangeDependencies
I/flutter (20121): BdidChangeDependencies

看吧這里第一次就會打印這些 Satet 對應(yīng)的 didChangeDependencies 函數(shù)呢,這是因?yàn)樵谝婚_始 Widget 對應(yīng)的 Element 為空的情況下會運(yùn)行如下代碼

  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      // When the type of a widget is changed between Stateful and Stateless via
      // hot reload, the element tree will end up in a partially invalid state.
      // That is, if the widget was a StatefulWidget and is now a StatelessWidget,
      // then the element tree currently contains a StatefulElement that is incorrectly
      // referencing a StatelessWidget (and likewise with StatelessElement).
      //
      // To avoid crashing due to type errors, we need to gently guide the invalid
      // element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
      // returns false which prevents us from trying to update the existing element
      // incorrectly.
      //
      // For the case where the widget becomes Stateful, we also need to avoid
      // accessing `StatelessElement.widget` as the cast on the getter will
      // cause a type error to be thrown. Here we avoid that by short-circuiting
      // the `Widget.canUpdate` check once `hasSameSuperclass` is false.
      assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }

    //省略以下代碼
}

在運(yùn)行到 updateChild 的時(shí)候 child 會為空(因?yàn)閺臎]運(yùn)行過)鲜锚,所以會運(yùn)行到 newChild = inflateWidget(newWidget, newSlot); 這一句代碼芜繁,如下:

 Element inflateWidget(Widget newWidget, Object? newSlot) {
    assert(newWidget != null);
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      final Element? newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        assert(newChild._parent == null);
        assert(() {
          _debugCheckForCycles(newChild);
          return true;
        }());
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        assert(newChild == updatedChild);
        return updatedChild!;
      }
    }
    final Element newChild = newWidget.createElement();
    assert(() {
      _debugCheckForCycles(newChild);
      return true;
    }());
    newChild.mount(this, newSlot);
    assert(newChild._lifecycleState == _ElementLifecycle.active);
    return newChild;
  }

其中會運(yùn)行到 final Element newChild = newWidget.createElement(); 這一句用對應(yīng)的Widget去創(chuàng)建對應(yīng)的Element蔬捷,隨后調(diào)用 newChild.mount(this, newSlot);

接下來到element.mount周拐,他主要有兩個(gè)運(yùn)行分支流程:

  • 一個(gè)是ComponentElement的,
  • 一個(gè)是RenderObjectElement的勾给,

我們最常見的就是 ComponentElement.mount 代碼如下:

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  assert(_child == null);
  assert(_active);
  _firstBuild();
  assert(_child != null);
}

我可以給大家先說明一下 mount 這個(gè)函數(shù)其主要目的接著就把新建的子element掛載到了element樹上,這個(gè)主要不是我們今天的內(nèi)容所以不展開講旅择,想看細(xì)節(jié)的請大家關(guān)注我之前寫的文章 <Flutter運(yùn)行原理篇之Build構(gòu)建的過程>

RenderObjectElement.mount

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);

  _renderObject = widget.createRenderObject(this);

  attachRenderObject(newSlot);
  _dirty = false;
}

此方法會創(chuàng)建一個(gè)RenderObject,之后就會把此RenderObject添加到RenderObject樹上柱蟀,想更清楚的話請大家關(guān)注我之前寫的博客<Flutter運(yùn)行原理篇之Build構(gòu)建的過程>

ComponentElement.mount 里面會運(yùn)行一個(gè) _firstBuild 方法:

StatefulElement._firstBuild 如下:

  @override
  void _firstBuild() {
    assert(state._debugLifecycleState == _StateLifecycle.created);
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
      assert(() {
        if (debugCheckForReturnedFuture is Future) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
            ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
            ErrorHint(
              'Rather than awaiting on asynchronous work directly inside of initState, '
              'call a separate method to do this work without awaiting it.',
            ),
          ]);
        }
        return true;
      }());
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    assert(() {
      state._debugLifecycleState = _StateLifecycle.initialized;
      return true;
    }());
    state.didChangeDependencies();
    assert(() {
      state._debugLifecycleState = _StateLifecycle.ready;
      return true;
    }());
    super._firstBuild();
  }

ComponentElement._firstBuild

  void _firstBuild() {
    rebuild();
  }

這里面會調(diào)用 state.didChangeDependencies(); 這個(gè)方法术瓮,這也就是我們重寫的State的didChangeDependencies方法

好了胞四,我們搞清楚了第一次運(yùn)行的時(shí)候 didChangeDependencies 方法的被調(diào)用的過程氓侧,現(xiàn)在我們來點(diǎn)擊下一上面那個(gè)Demo的按鈕,看看他又做了什么呢独郎?

我們點(diǎn)擊了按鈕調(diào)用了 setState 方法,這個(gè)方法會使build方法運(yùn)行導(dǎo)致C的child被改變了顽铸,改為了SizedBox:

body: bDependenciesShouldChange ?
            Container(
              height: 500,
              alignment: Alignment.centerLeft,
              child: C(child: B()),
            )
            : Container(
              height: 500,
              alignment: Alignment.centerLeft,
              child: C(child: SizedBox(width: 20, height: 50, child: B())),
          )

熟悉Build運(yùn)行流程的同學(xué)應(yīng)該知道谓松,setState 這個(gè)方法會觸發(fā)到 updateChild 這個(gè)方法,上面有代碼我就不貼了优质,這個(gè)方法里面會進(jìn)行一個(gè)判斷,判斷 hasSameSuperclass 變量是否為false避乏,如果為false則會觸發(fā) inflateWidget 進(jìn)行Element的重構(gòu),也就是我們上面分析的步驟铆帽,我這里給大家貼一個(gè)圖會比較明顯:

image.png

大家看看,點(diǎn)擊了按鈕以后導(dǎo)致 hasSameSuperclass 變量判斷為false(意思是他們不是同一個(gè)父親),之后導(dǎo)致inflateWidget(SizeBox,newSlot) , 之后再會導(dǎo)致 SizeBox 的child為null冯键,再會觸發(fā)B進(jìn)行inflateWidget方法重構(gòu)其對應(yīng)的Element

邏輯部分都在 updateChild 函數(shù)里面:

if (child != null) {
      bool hasSameSuperclass = true;
      //省略部分代碼
}else{
      newChild = inflateWidget(newWidget, newSlot); //這里面的newWidget是B
}

所以這樣就會觸發(fā)BState 對應(yīng)的 didChangeDependencies 方法

好了,到這里你應(yīng)該就明白了為什么網(wǎng)上流傳的結(jié)論 父級結(jié)構(gòu)中的層級發(fā)生變化時(shí)didChangeDependencies被調(diào)用 這個(gè)結(jié)論的到底是怎么一回事了庸汗,其實(shí)這個(gè)會觸發(fā)其對應(yīng)的 Element重構(gòu) 導(dǎo)致該方法被調(diào)用

好了惫确,既然我們已經(jīng)搞清楚了調(diào)用的規(guī)則,我們不妨再來看看還會不會有別的情況會觸發(fā)呢蚯舱,我們再來看看 updateChild 方法里面的邏輯判斷:

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }

    //省略以下代碼
}

我們看到 (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) 這一行說明如果他們的父親沒有變化改化,但是 Widget.canUpdate 的返回發(fā)生了變化的話,那么他應(yīng)該也會觸發(fā)下面的 newChild = inflateWidget(newWidget, newSlot); 方法導(dǎo)致 didChangeDependencies 方法被調(diào)用枉昏,我們看看這個(gè)方法:

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

我們只需要改變他對應(yīng)的key就可以實(shí)現(xiàn)我們的猜想了不是嗎?

所以我列出了下面的例子說明改情況:

import 'package:flutter/material.dart';

class TestDidChangeDependencies3 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TestDidChangeDependencies3State();
}

class TestDidChangeDependencies3State extends State<TestDidChangeDependencies3> {
  bool bDependenciesShouldChange = false;
  late Key k;
  @override
  void initState() {
    super.initState();
    k = Key('hello');
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        body: Container(
              height: 500,
              alignment: Alignment.centerLeft,
              child: C(key:k,child: B()),
            ),
              floatingActionButton: FloatingActionButton(
                onPressed: _incrementCounter,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              )
    );
  }

  void _incrementCounter(){
    setState(() {
      k = Key('world');
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("AdidChangeDependencies");
  }
}

class B extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => BState();
}

class BState extends State<B> {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("Test"));
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("BdidChangeDependencies");
  }
}

class C extends StatefulWidget {
  final Widget child;

  C({Key? key, required this.child}) : super(key: key);

  @override
  State<StatefulWidget> createState() => CState();
}

class CState extends State<C> {
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("CdidChangeDependencies");
  }
}

例子很簡單不詳細(xì)講解了溯乒,我們點(diǎn)擊了按鈕導(dǎo)致了C的key發(fā)生了變化導(dǎo)致C重構(gòu)其Element也間接導(dǎo)致了B重構(gòu)了對應(yīng)的Element導(dǎo)致了如下的打印

I/flutter (22761): AdidChangeDependencies
I/flutter (22761): CdidChangeDependencies
I/flutter (22761): BdidChangeDependencies
I/flutter (22761): CdidChangeDependencies
I/flutter (22761): BdidChangeDependencies

好了逻住,這里我們又學(xué)到了在不改變父類結(jié)構(gòu)的情況下冀瓦,如果改變Key也會導(dǎo)致didChangeDependencies被調(diào)用,但是其本質(zhì)還是我一開始總結(jié)的:那就是在它的 State 對象對應(yīng)的 Element 被 createElement() 的時(shí)候并且被Mount的時(shí)候就會被調(diào)用狂巢,簡單就是說當(dāng)這個(gè)對應(yīng)的 Element 被重新創(chuàng)建的時(shí)候就會被調(diào)用

好了看到這里你可能會松了一口氣,總算明白了 didChangeDependencies 被調(diào)用背后的邏輯了腔呜,但是我要告訴你的是還沒有完哦跟束,??這只是最普通的情況之一哦,還有一種更常見的情況那就是使用 InheritedWidget 的時(shí)候也會觸發(fā)調(diào)用真友,這個(gè)是一個(gè)非常常用的控件站绪,我們作為要深入flutter的開發(fā)者來說坠非,不能只看簡單的情況,這個(gè)控件也要必須搞清楚赵辕,那么在該文章的下半結(jié)我們來聊一聊 《InheritedWidget 以及對應(yīng)的運(yùn)行函數(shù)極其背后的邏輯》

我們先來看看概念:InheritedWidget是 Flutter 中非常重要的一個(gè)功能型組件笆怠,它提供了一種在 widget 樹中從上到下共享數(shù)據(jù)的方式,比如我們在應(yīng)用的根 widget 中通過InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget 中來獲取該共享的數(shù)據(jù)混卵!

State對象有一個(gè)didChangeDependencies回調(diào)睦霎,它會在“依賴”發(fā)生變化時(shí)被Flutter 框架調(diào)用副女。而這個(gè)“依賴”指的就是子 widget 是否使用了父 widget 中InheritedWidget的數(shù)據(jù)诀浪!如果使用了,則代表子 widget 有依賴刑然;如果沒有使用則代表沒有依賴。

我們借用網(wǎng)上的Demo先舉一個(gè)例子:

import 'package:flutter/material.dart';

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  final int data; //需要在子樹中共享的數(shù)據(jù)暇务,保存點(diǎn)擊次數(shù)

  //定義一個(gè)便捷方法泼掠,方便子樹中的widget獲取共享數(shù)據(jù)
  static ShareDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //該回調(diào)決定當(dāng)data發(fā)生變化時(shí),是否通知子樹中依賴data的Widget
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    return old.data != data;
  }
}

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享數(shù)據(jù)
    return Text(ShareDataWidget.of(context)!.data.toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時(shí)會被調(diào)用垦细。
    //如果build中沒有依賴InheritedWidget择镇,則此回調(diào)不會被調(diào)用。
    print("Dependencies change");
  }
}

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依賴ShareDataWidget
            ),
            ElevatedButton(
              child: Text("Increment"),
              //每點(diǎn)擊一次括改,將count自增腻豌,然后重新build,ShareDataWidget的data將被更新
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}
image.png

我們點(diǎn)擊一次會加1,并且會打印一次 Dependencies change

這種情況不像上面我們分析的改變結(jié)構(gòu)的情況了,他是怎么回事呢饲梭?

我們來看看點(diǎn)擊了按鈕 _TestWidget 是怎么拿到數(shù)據(jù)的乘盖,如下:

  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享數(shù)據(jù)
    return Text(ShareDataWidget.of(context)!.data.toString());
  }
  //定義一個(gè)便捷方法,方便子樹中的widget獲取共享數(shù)據(jù)
  static ShareDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

背后邏輯就在于 context.dependOnInheritedWidgetOfExactType<T>() 這句話:

Element.dependOnInheritedWidgetOfExactType

  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

這里會從 _inheritedWidgets![T] 里面去取出對應(yīng)的Element憔涉,那這個(gè)Element什么時(shí)候被添加進(jìn)這個(gè)Map里面的呢订框,答案在于mount方法的調(diào)用邏輯:

Element.mount

  @mustCallSuper
  void mount(Element? parent, Object? newSlot) {
   //省略部分代碼
    _updateInheritance();
  }

InheritedElement._updateInheritance

  @override
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets![widget.runtimeType] = this; //注意這一句話
  }

也就是說在InheritedElement被mount的時(shí)候會把自己添加進(jìn)入這個(gè)Map _inheritedWidgets![widget.runtimeType] = this; ,所以在取的時(shí)候你可以拿到這個(gè)InheritedElement對象

接下來再看看 dependOnInheritedElement 這個(gè)函數(shù)做了什么呢兜叨?

Element.dependOnInheritedElement

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }
  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }

很簡單不細(xì)說穿扳,就是把_TestWidget對應(yīng)的Element添加進(jìn)了_dependents 這個(gè)Map里面,準(zhǔn)備作為觸發(fā)的時(shí)候使用

那什么時(shí)候觸發(fā)呢国旷,也就是在我們點(diǎn)擊按鈕以后會觸發(fā)setState間接的觸發(fā)InheritedElement的update方法更新的時(shí)候

InheritedElement.updated

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

ProxyElement.updated

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

InheritedElement.notifyClients

  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);  //注意這一句
    }
  }

會取出dependent矛物,也就是我們存的_TestWidget對應(yīng)的Element,然后調(diào)用notifyDependent

  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

StatefulElement.didChangeDependencies

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _didChangeDependencies = true;
  }

這里這一句很重要_didChangeDependencies = true; 這里的意思是把_TestWidget對應(yīng)的Element的這個(gè)_didChangeDependencies屬性變?yōu)閠rue跪但,等待_TestWidget對應(yīng)的Element也就是StatefulElement對應(yīng)的performRebuild方法調(diào)用的時(shí)候觸發(fā)

StatefulElement.performRebuild

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

這里很明顯了state.didChangeDependencies();這一句被調(diào)用了履羞,說明我們重載的didChangeDependencies方法就會被調(diào)用了

好了,到這里我們把 didChangeDependencies 方法什么時(shí)候被調(diào)用說得差不多了屡久,但是大家還需要注意一個(gè)點(diǎn)就是 updateShouldNotify 這個(gè)函數(shù)的作用忆首,這個(gè)函數(shù)網(wǎng)上說的是:是否通知子樹中依賴的Widget調(diào)用其對應(yīng)的 didChangeDependencies 方法,返回true則為通知被环,其實(shí)這里還有更深一層的含義在里面糙及,就是這個(gè)返回值直接影響了依賴 InheritedWidget 對應(yīng)對象的build方法是否執(zhí)行

最后再仔細(xì)看看,確實(shí)如果InheritedWidget對應(yīng)的updateShouldNotify方法返回為false的話筛欢,那么super.updated(oldWidget);方法將無法調(diào)用

InheritedElement.updated

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

InheritedElement繼承至ProxyElement

ProxyElement.updated

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

InheritedElement.notifyClients

  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}

這里說得很清楚了浸锨,如果updateShouldNotify為false的話notifyDependent將不會被調(diào)用,那么State對應(yīng)的didChangeDependencies也不會被調(diào)用

這里還沒有完版姑,接著往下看柱搜,notifyDependent會調(diào)用到 didChangeDependencies方法:

Element.didChangeDependencies

  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

最終就不會到這里,也不會調(diào)用到依賴 InheritedWidget 對應(yīng)對象的 markNeedsBuild 方法漠酿,所以他們的build方法無法被調(diào)用

ProxyElement.build

  @override
  Widget build() => widget.child;

究其原因在于ProxyElement對應(yīng)的build方法冯凹,他這個(gè)方法與其他的build方法不一樣,其他的build方法如下 Widget build() => state.build(this); 都會調(diào)用重寫的build方法炒嘲,他這個(gè)build只是返回widget.child宇姚,并沒有觸發(fā)build方法調(diào)用所以導(dǎo)致了他的build不具備構(gòu)建的能力,依賴 InheritedWidget 對應(yīng)對象要想更新就不能使updateShouldNotify返回false

這里是卡了我一個(gè)比較久的問題夫凸,當(dāng)時(shí)確實(shí)想了半天才發(fā)現(xiàn)這里

好了浑劳,這篇文章說到這里已經(jīng)差不多了,相信能讀玩的小伙伴一定是具有一定耐心的
我也不在往下深究了夭拌,那不然結(jié)束不了了魔熏,總之今天我們講了兩個(gè)大致的內(nèi)容:

  • didChangeDependencies方法調(diào)用背后的邏輯
  • InheritedWidget的基本使用

希望能給大家?guī)韼椭匝剩矚g我的文章的話歡迎給我點(diǎn)贊或者留言,我看到的話一定第一時(shí)間給你回復(fù)的謝謝大家蒜绽,你的點(diǎn)贊加留言是我持續(xù)更新的動(dòng)力镶骗,謝謝大家···

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躲雅,隨后出現(xiàn)的幾起案子鼎姊,更是在濱河造成了極大的恐慌,老刑警劉巖相赁,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件相寇,死亡現(xiàn)場離奇詭異,居然都是意外死亡钮科,警方通過查閱死者的電腦和手機(jī)唤衫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绵脯,“玉大人佳励,你說我怎么就攤上這事∏欤” “怎么了植兰?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長璃吧。 經(jīng)常有香客問我,道長废境,這世上最難降的妖魔是什么畜挨? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮噩凹,結(jié)果婚禮上巴元,老公的妹妹穿的比我還像新娘。我一直安慰自己驮宴,他們只是感情好逮刨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堵泽,像睡著了一般修己。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迎罗,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天睬愤,我揣著相機(jī)與錄音,去河邊找鬼纹安。 笑死尤辱,一個(gè)胖子當(dāng)著我的面吹牛砂豌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播光督,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼阳距,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了结借?” 一聲冷哼從身側(cè)響起筐摘,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎映跟,沒想到半個(gè)月后蓄拣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡努隙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年球恤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荸镊。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咽斧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躬存,到底是詐尸還是另有隱情张惹,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布岭洲,位于F島的核電站宛逗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盾剩。R本人自食惡果不足惜雷激,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望告私。 院中可真熱鬧屎暇,春花似錦、人聲如沸驻粟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜀撑。三九已至挤巡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屯掖,已是汗流浹背玄柏。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贴铜,地道東北人粪摘。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓瀑晒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徘意。 傳聞我的和親對象是個(gè)殘疾皇子苔悦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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