主頁 > 後端開發 > 【老孟Flutter】原始碼分析系列之InheritedWidget

【老孟Flutter】原始碼分析系列之InheritedWidget

2021-01-24 17:29:34 後端開發

老孟導讀:這是2021年原始碼系列的第一篇文章,其實原始碼系列的文章不是特別受歡迎,一個原因是原理性的知識非常枯燥,我自己看原始碼的時候特別有感觸,二是想把原始碼分析講的通俗易懂非常困難,自己明白 和 讓別人聽懂完全是兩回事,不過我依然會堅持 Flutter 原始碼系列的文章,提高自己的技術水平的同時,也希望大家識訓一些知識,

為了使原始碼系列的文章不那么枯燥,文章中會有很多流程圖,流程圖比純文字更直觀,一圖勝千言,

我也是第一次寫原始碼系列的文章,如果文章哪里有不對的地方請告訴我,雖然我也不一定聽??,開個玩笑,

希望大家來個 ,您的 是我寫下去的巨大動力??,

所有原始碼系列文章都會分享到我個人博客:http://laomengit.com/

正文

注意:使用的 Flutter 版本 和 Dart 版本如下:

Flutter 1.22.4 ? channel stable ? https://github.com/flutter/flutter.git
Framework ? revision 1aafb3a8b9 (6 weeks ago) ? 2020-11-13 09:59:28 -0800
Engine ? revision 2c956a31c0
Tools ? Dart 2.10.4

不同的版本可能有所不同,請注意版本之間的區別,

首先, InheritedWidget 是一個非常重要非常重要非常重要的組件,重要的事情說3遍??,系統中很多功能都是功能型組件都是通過 InheritedWidget 實作的,著名的 Provider 狀態管理框架也是基于 InheritedWidget 實作的,因此不管是作業中,還是面試,InheritedWidget 組件的原理及使用場景都是考察的重點,

此篇文章包括如下幾個部分:

  • InheritedWidget 組件簡介,是什么場景下使用 InheritedWidget?
  • InheritedWidget 基本用法,
  • InheritedWidget 原始碼分析,

InheritedWidget 組件簡介

InheritedWidget 組件是功能型組件,提供了沿樹向下,共享資料的功能,即子組件可以獲取父組件(InheritedWidget 的子類)的資料,通過BuildContext.dependOnInheritedWidgetOfExactType 獲取,

InheritedWidget 組件的共享資料是沿著樹從上到下,是否聯想到 Notification,Notification 正好與 InheritedWidget 傳遞方向相反,Notification 是沿著樹從下到上,兩者功能的實作都是子節點主動發起,InheritedWidget 組件的子節點主動查找父節點上 InheritedWidget 共享的資料,Notification 也是子節點主動發起通知,沿著樹向上通知,

Notification 也是 Flutter 中非常重要的,后面會有專門的文章詳細介紹,此篇不做介紹,

那么什么樣的場景適合使用 InheritedWidget 呢? 通常App會有登錄用戶資訊,登錄用戶資訊為全域共享資訊,想象一下,子組件要如何獲取登錄用戶資訊?將上面的場景抽象一下,有一顆組件樹,A、H 組件依賴同一資料,如下:

A、H 組件要如何獲取到資料呢?

有一種實作方式是 通過建構式透傳,資料通過A傳遞給B,B傳遞給C、E,C和E在傳遞給F、H,如下圖虛線的傳遞:

反應到代碼上就是:

return A(
  data:data
  child:B(
    data:data
    child:C(
      data:data
      child:F(
        data:data
      )   
    )
  )
);

這樣的實作缺點非常明顯,B、C組件不需要 data 資料,如果組件樹比較深的話,那將是噩夢,

為了處理此問題,Flutter Framework 提供了 InheritedWidget 組件,InheritedWidget 組件的子組件可以直接獲取資料,如下圖:

InheritedWidget 組件的所有子組件都可以直接通過 BuildContext.dependOnInheritedWidgetOfExactType 獲取資料,

InheritedWidget 基本用法

上面分析了 InheritedWidget 組件的使用場景,下面用一個最簡單的 demo 展示如何使用 InheritedWidget 組件,

定一個用戶資訊共享資料的物體類,任何子組件都可以獲取用戶資訊,用戶資訊物體類:

class UserInfo {
  String name;
  int age;

  UserInfo({this.name, this.age});

  @override
  bool operator ==(Object other) {
    if (!(other is UserInfo)) {
      return false;
    }
    var old = other as UserInfo;
    return name == old.name && age == old.age;
  }
}

UserInfo 類重寫了 == 運算子,是為了后面資料是否發生變化做判斷,

定義共享 UserInfo 資料的 InheritedWidget 組件,

class MyInheritedWidget extends InheritedWidget {
  final UserInfo userInfo;

  MyInheritedWidget({this.userInfo, Widget child}):super(child: child);

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    return oldWidget.userInfo != userInfo;
  }
}

這里有兩個地方需要注意:

  1. 靜態(static) of 方法,這個方法不是必須的,但一般都會添加此方法,方便其子組件呼叫,當然也可以直接在子組件中使用 context.dependOnInheritedWidgetOfExactType 方法,不加 of 方法,子組件呼叫如下:

    class F extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        var myInheritedWidget =
            context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
        return Text('name:${myInheritedWidget.userInfo.name}');
      }
    }
    

    添加靜態(static) of 方法,用法如下:

    class F extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
      }
    }
    

    我們經常使用的 MediaQuery.of(context)Theme.of(context) 方法都是系統封裝的此方法,

  2. updateShouldNotify 方法必須重寫,此方法是判斷新的共享資料和原資料是否一致,是否將通知傳遞給所有子組件(已注冊),重建此組件時,有時需要重建繼承 InheritedWidget 組件的組件,但有時不需要, 例如,如果此組件所保存的資料與“ oldWidget”所保存的資料相同,則我們無需重建繼承了“ oldWidget”所保存的資料的組件,

使用 MyInheritedWidget 組件:

class InheritedWidgetDemo extends StatefulWidget {
  @override
  _InheritedWidgetDemoState createState() => _InheritedWidgetDemoState();
}

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: UserInfo(name: '老孟', age: 18),
          child: A(
            child: F(),
          ),
        ),
      ),
    );
  }
}

A 組件代碼:

class A extends StatelessWidget {
  final Widget child;

  const A({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: child,
    );
  }
}

F 組件代碼:

class F extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
  }
}

上面代碼構成的組件樹為(無關的節點已忽略比如Scaffold、Center等):

注意: A 組件是為了表示樹的深度,此 Demo 中將其簡化了,僅僅設定了一層,也可以設多多層,

運行效果:

下面修改資料并重繪UI,下面的代碼僅能用于Demo,是為了演示方便,千萬不要用于實際專案,因為下面的寫法有巨大的性能問題,因為重建了 InheritedWidget 組件下的所有子組件,重要的事情說3遍:下面演示Demo千萬不要用于實際專案千萬不要用于實際專案千萬不要用于實際專案,文章最后我會給出正確用法,

修改 _InheritedWidgetDemoState

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo = UserInfo(name: '老孟1', age: 18);
          });
        },
      ),
    );
  }
}

點擊按鈕的時候,UI重繪了,但請重點看右側 rebuild stats 部分,每點擊一次按鈕,MyInheritedWidget 組件及其子組件全部 重新構建(rebuild),但 A 組件并不依賴于 MyInheritedWidget 共享資料,理想情況下不應該 rebuild,實際專案中,樹的結構會比這個復雜的多,因此全部 rebuild 會造成性能問題,這也是開頭說千萬不要將此方式用于實際專案,網上充斥著大量此種用法的文章,后面會給出正確用法,正確用法比較復雜,而且涉及其他相關知識,所以此處的Demo僅用于學習 InheritedWidget,

大家是否還記得 Stateful 組件的生命周期 文章中介紹的 didChangeDependencies 生命周期,對其的介紹如下:

didChangeDependencies 方法在 initState 之后由 Framework 立即呼叫,另外,當此 State 物件的依賴項更改時被呼叫,比如其所依賴的 InheritedWidget 發生變化時, Framework 會呼叫此方法通知組件發生變化,

下面將 A 和 F 組件改為 StatefulWidget 組件:

class F extends StatefulWidget {
  @override
  _FState createState() => _FState();
}

class _FState extends State<F> {
  @override
  void initState() {
    super.initState();
    print('F initState');
  }

  @override
  Widget build(BuildContext context) {
    print('F build');
    return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('F didChangeDependencies');
  }

  @override
  void dispose() {
    super.dispose();
    print('F dispose');
  }
}

class A extends StatefulWidget {
  final Widget child;

  const A({Key key, this.child}) : super(key: key);

  @override
  _AState createState() => _AState();
}

class _AState extends State<A> {
  @override
  void initState() {
    super.initState();
    print('A initState');
  }

  @override
  Widget build(BuildContext context) {
    print('A build');
    return Center(
      child: widget.child,
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('A didChangeDependencies');
  }

  @override
  void dispose() {
    super.dispose();
    print('A dispose');
  }
}

給各個生命周期添加日志列印,重新運行,點擊按鈕,輸出日志如下:

flutter: A build
flutter: F didChangeDependencies
flutter: F build

因此,依賴 MyInheritedWidget 組件的 F 組件呼叫 didChangeDependencies 方法,而 A 組件沒有呼叫 didChangeDependencies 方法,因為 A 沒有依賴 MyInheritedWidget 組件,

下面再說一個非常容易忽略的地方 MyInheritedWidget.updateShouldNotify方法,一般這樣寫:

@override
bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
  return oldWidget.userInfo != userInfo;
}

這樣寫有什么問題嗎?如果資料(userInfo)是自定義的物體類且未在 UserInfo 中重寫 ==,那么極大概率出現有問題,因為不重寫 == 運算子方法,使用 != 判斷是否相等的時候判斷的是兩個物件的記憶體地址,下面將 UserInfo 中 == 方法去掉,

class UserInfo {
  String name;
  int age;

  UserInfo({this.name, this.age});
}

修改 _InheritedWidgetDemoState 類:

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),
    );
  }
}

修改 updateShouldNotify 方法,添加日志列印:

@override
bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
  bool flag = oldWidget.userInfo != userInfo;
  print('updateShouldNotify:$flag');
  return flag;
}

點擊按鈕,_userInfo 物件參考發生了變化,但其值( name 和 age)都沒有發生變化,updateShouldNotify 應該回傳false,但實際列印的結果:

flutter: updateShouldNotify:true
flutter: A build
flutter: F didChangeDependencies
flutter: F build

實際回傳了 true,因為前后 _userInfo 物件參考發生了變化,在 UserInfo 中重寫 ==,比較具體的name 和 age 是否相等:

@override
bool operator ==(Object other) {
  if (!(other is UserInfo)) {
    return false;
  }
  var old = other as UserInfo;
  return name == old.name && age == old.age;
}

再次運行,日志如下:

flutter: updateShouldNotify:false
flutter: A build
flutter: F build

還有一種錯誤寫法:

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo.name = '老孟1';
            // _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),
    );
  }
}

重點看這部分修改:

floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo.name = '老孟1';
            // _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),

_userInfo = UserInfo(name: '老孟', age: 18) 修改為 _userInfo.name = '老孟1',猜猜 updateShouldNotify 回傳是 true or false?

運行日志:

flutter: updateShouldNotify:false
flutter: A build
flutter: F build

是不是感覺非常不可思議,兩次的 name 值不一樣啊?

那是因為 _userInfo.name = '老孟1' 也修改了 oldWidget 的_userInfo,前后兩次都指向了同一個物件參考,

很多人應該會有這樣一個疑問,假設設定 updateShouldNotify 回傳false,點擊的時候UI也會更改,因為整顆樹都 rebuild 了,那么 updateShouldNotify 由什么意義呢?

肯定是有意義的,看如下場景,F 組件使用 InheritedWidget 的共享資料訪問服務器介面,獲取服務器資料并展示,如果 updateShouldNotify 回傳 false,那么 F 組件 rebuild 時只會執行 build 函式,而訪問服務器介面是一個耗時作業,考慮性能因素,不能將訪問服務器介面放在 build 函式中,那么 InheritedWidget 資料的更新就無法更新其依賴的組件,而 updateShouldNotify 回傳 true時, F 組件 rebuild 時會執行 didChangeDependenciesbuild 函式,此時可以將訪問服務器介面放在 didChangeDependencies 函式中,這也是 didChangeDependencies 生命周期存在的意義,

下面重點來了,那么如何正確使用 InheritedWidget 組件,答案是 InheritedWidget + ValueNotifier,關于 ****的用法可以到我的個人博客中查看,地址:http://laomengit.com/flutter/widgets/ValueListenableBuilder.html ,這里不詳細展開介紹,

修改 MyInheritedWidget 代碼:

class MyInheritedWidget extends InheritedWidget {
  ValueNotifier<UserInfo> _valueNotifier;

  ValueNotifier<UserInfo> get valueNotifier => _valueNotifier;

  MyInheritedWidget(UserInfo userInfo, {Widget child}) : super(child: child) {
    _valueNotifier = ValueNotifier<UserInfo>(userInfo);
  }

  static MyInheritedWidget of(BuildContext context) {
    return context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>().widget;
  }

  void updateData(UserInfo info) {
    _valueNotifier.value = https://www.cnblogs.com/mengqd/p/info;
  }

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    return false;
  }
}

主要的變化是:

  • 共享資料由 UserInfo 型別變為了 ValueNotifier<UserInfo>,

  • 增加了更新資料的方法 updateData

  • updateShouldNotify 方法直接回傳了 false,因為資料的更新通過 ValueNotifier 實作,

  • 靜態方法 of

    static MyInheritedWidget of(BuildContext context) {
    	return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
    }
    

    改為

    static MyInheritedWidget of(BuildContext context) {
      return context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>().widget;
    }
    

    區別是 dependOnInheritedWidgetOfExactType 注冊了依賴關系,而 getElementForInheritedWidgetOfExactType 未注冊,后面的原始碼部分會詳細分析,

修改 F 組件的代碼:

class F extends StatefulWidget {
  @override
  _FState createState() => _FState();
}

class _FState extends State<F> {
  @override
  void initState() {
    super.initState();
    print('F initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('F didChangeDependencies');
  }

  @override
  Widget build(BuildContext context) {
    print('F build');
    return ValueListenableBuilder(
      builder: (context, UserInfo value, child) {
        return Text('${value.name}');
      },
      valueListenable: MyInheritedWidget.of(context).valueNotifier,
    );
  }

  @override
  void dispose() {
    super.dispose();
    print('F dispose');
  }
}

變化:

  • 依賴共享資料的Text組件添加了 ValueListenableBuilder 組件,資料發生變化時,重新執行 builder ,

_InheritedWidgetDemoState 代碼修改如下:

@override
Widget build(BuildContext context) {
  return MyInheritedWidget(UserInfo(name: '老孟', age: 18), child: Builder(
    builder: (context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('InheritedWidget Demo'),
        ),
        body: Center(
          child: A(
            child: F(),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            MyInheritedWidget.of(context)
                .updateData(UserInfo(name: '老孟${_clickCount++}', age: 18));
          },
        ),
      );
    },
  ));
}

運行效果:

重點看 rebuild 的組件,無關的組件(比如 A)沒有 rebuild

當然也可以使用 Provider 實作子組件更新,增加 UserInfoModel

class UserInfoModel extends ChangeNotifier {
  UserInfoModel(this._userInfo);

  UserInfo _userInfo;

  UserInfo get userInfo => _userInfo;

  void update(UserInfo userInfo) {
    _userInfo = userInfo;
    notifyListeners();
  }
}

修改 _InheritedWidgetDemoState

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  int _clickCount =0;

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
            create: (_) => UserInfoModel(UserInfo(name: '老孟', age: 18))),
      ],
      builder: (context, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text('InheritedWidget Demo'),
          ),
          body: Center(
            child: A(
              child: F(),
            ),
          ),
          floatingActionButton: Consumer<UserInfoModel>(
            builder: (ctx, userInfoModel, child) {
              return FloatingActionButton(
                child: child,
                onPressed: () {
                  userInfoModel.update(UserInfo(name: '老孟${_clickCount++}', age: 18));
                },
              );
            },
          ),
        );
      },
    );
  }
}

InheritedWidget 原始碼分析

分析原始碼的時候一定要先想想,如果是我來實作這個組件,要如何實作? InheritedWidget 組件主要實作了兩個功能:

  • 如何實作系結依賴它的子組件
  • 如何通知子組件自己發生了更改,

如何實作系結依賴它的子組件

依賴 InheritedWidget 的子組件如何獲取 InheritedWidget 組件的共享資料?首先查看獲取共享資料的方法:

context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();

這段代碼獲取 MyInheritedWidget 實體,dependOnInheritedWidgetOfExactType 方法是 BuildContext 的方法,Element 實作了此方法:

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

從上面的源代碼可以看出,首先到 _inheritedWidgets 中查找指定的 InheritedElement,_inheritedWidgets 這個 Map 是哪里來的?什么時候被初始化的?看下 _inheritedWidgets 屬性的定義:

Map<Type, InheritedElement> _inheritedWidgets;

查找其參考和賦值的源代碼:

@override
void _updateInheritance() {
  assert(_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 中,但此方法在 Element 中也實作了:

void _updateInheritance() {
  assert(_active);
  _inheritedWidgets = _parent?._inheritedWidgets;
}

上面的代碼說明了非 InheritedElement 的 Element 中 _inheritedWidgets 等于父組件的 _inheritedWidgets,而 InheritedElement 會將自身添加到 _inheritedWidgets 中,系統通過此方式將組件和 InheritedWidgets 的依賴關系層層向下傳遞,每一個 Element 中都含有 _inheritedWidgets 集合,此集合中包含了此組件的父組件且是InheritedWidgets 組件的參考關系,

那么是什么時候執行的 _updateInheritance 方法的呢?通過查找其參考,發現在 mountactivate 中呼叫了 _updateInheritance 方法,關于 mountactivate 階段可以查看 Stateful 組件的生命周期 文章,

下面查看 dependOnInheritedElement 方法,在查找到依賴的 InheritedElement 后,執行 dependOnInheritedElement 方法,源代碼如下:

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
  assert(ancestor != null);
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies.add(ancestor);
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget;
}

updateDependencies 方法源代碼如下:

@protected
void updateDependencies(Element dependent, Object aspect) {
  setDependencies(dependent, null);
}

@protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }
  

上面的代碼就是向 _dependents 中添加注冊, InheritedWidget 組件更新時可以更具此串列通知子組件,

再來看下的代碼:

@Deprecated(
  'Use getElementForInheritedWidgetOfExactType instead. '
  'This feature was deprecated after v1.12.1.'
)
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
  return ancestor;
}

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}

在 v1.12.1 版本以前使用 ancestorInheritedElementForWidgetOfExactType 方法,現在使用 getElementForInheritedWidgetOfExactType 方法,此方法和 dependOnInheritedWidgetOfExactType 方法的唯一卻不就是 getElementForInheritedWidgetOfExactType 方法沒有注冊,也就是使用 getElementForInheritedWidgetOfExactType 方法獲取共享資料的子組件,不會在 InheritedWidget 組件重建時呼叫 didChangeDependencies 方法,

下面看看為什么 InheritedWidget 組件資料方式變化,重建時會呼叫其 didChangeDependencies 方法?

當組件發生變化時會呼叫 update方法:

@override
void update(ProxyWidget newWidget) {
  final ProxyWidget oldWidget = widget;
  assert(widget != null);
  assert(widget != newWidget);
  super.update(newWidget);
  assert(widget == newWidget);
  updated(oldWidget);
  _dirty = true;
  rebuild();
}

InheritedElement 重寫了 updated 方法:

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

updateShouldNotify 回傳 true時,執行更新操作,而其父類的 updated 方法如下:

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

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);
  }
}

遍歷 _dependents,上面已經介紹,_dependents 是依賴它的子組件集合,遍歷呼叫 notifyDependent 方法:

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

這里呼叫了 didChangeDependencies 方法,這也是 InheritedWidget 組件發生變化,重建時執行生命周期 didChangeDependencies

上面的代碼都是在 InheritedElement 中的,在看下 InheritedWidget 的源代碼:

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

這個類非常簡單,創建了一個 InheritedElement,定義了一個 updateShouldNotify 方法(上面已經詳細介紹此方法的作用),子類需要重寫,

通過上面的原始碼決議,子組件獲取共享資料時,實際是直接在 _inheritedWidgets 集合中匹配的,通過斷點也可以查看其中的內容:

總結

通過上面的分析,InheritedWidget 組件流程如下:

說明:

  • 在當前組件的 mountactivate 階段,系統呼叫 _updateInheritance 方法,將 InheritedWidget 型別的父組件添加到 _inheritedWidgets 集合中,
  • 子組件執行 dependOnInheritedWidgetOfExactType 方法時,從 _inheritedWidgets 集合中獲取指定 InheritedWidget 型別的父組件,并將當前組件注冊到 InheritedWidget 型別父組件的 _dependents 集合中,
  • InheritedWidget 組件資料發生變化(updateShouldNotify 方法回傳true),重建時,InheritedWidget 組件遍歷 _dependents 集合中所有依賴的子組件,執行子組件的 didChangeDependencies 的方法,

一點看法

那么為什么是在當前組件的中保存這樣一個 Map 集合,而不是依次向上查找呢(我最開始的想法)?

下面是我個人的一點看法,如果你有不同的看法,歡迎一起討論:

當前組件的中保存這樣一個 Map 集合,獲取共享資料時直接定位依賴的 InheritedWidget,復雜度 O(1) ,

而依次向上查找的復雜度是 O(n),樹的結構越深,消耗時間越長,復雜度線性增長,

交流

老孟Flutter博客(330個控制元件用法+實戰入門系列文章):http://laomengit.com

添加微信或者公眾號領取 《330個控制元件大全》和 《Flutter 實戰》PDF,

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/251359.html

標籤:Dart

上一篇:零基礎想要轉行成為程式員?這幾點你要知道

下一篇:【老孟Flutter】如何提高Flutter應用程式的性能

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more