Flutter源碼分析系列(二):Widget數(shù)據(jù)共享之InheritedWidget

簡(jiǎn)介

業(yè)務(wù)開發(fā)中經(jīng)常會(huì)碰到這樣的情況,多個(gè)Widget需要同步同一份全局?jǐn)?shù)據(jù)给涕,比如點(diǎn)贊數(shù)、評(píng)論數(shù)额获、夜間模式等等够庙。在安卓中,一般的實(shí)現(xiàn)方式是觀察者模式抄邀,需要開發(fā)者自行實(shí)現(xiàn)并維護(hù)觀察者的列表耘眨。在flutter中,原生提供了用于Widget間共享數(shù)據(jù)的InheritedWidget境肾,當(dāng)InheritedWidget發(fā)生變化時(shí)剔难,它的子樹中所有依賴了它的數(shù)據(jù)的Widget都會(huì)進(jìn)行rebuild,這使得開發(fā)者省去了維護(hù)數(shù)據(jù)同步邏輯的麻煩准夷。

使用范例

先來看下官方注釋中的范例:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  }) : assert(color != null),
       assert(child != null),
       super(key: key, child: child);
       
  final Color color;
  
  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }
  
  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

實(shí)現(xiàn)比較簡(jiǎn)單钥飞,直接繼承InheritedWidget即可,類中的color即為需要共享的數(shù)據(jù)衫嵌。
此外需要實(shí)現(xiàn)updateShouldNotify方法读宙,當(dāng)InheritedWidget被更新時(shí),該方法會(huì)被調(diào)用楔绞,并傳入更新之前的Widget對(duì)象结闸,該方法內(nèi)需要實(shí)現(xiàn)新舊數(shù)據(jù)的比較唇兑,若數(shù)據(jù)不同需要通知依賴的Widget更新,則返回true桦锄。
使用時(shí)扎附,可以通過FrogColor.of(context).color獲取到它的值。
下面舉一個(gè)具體的例子结耀,簡(jiǎn)單的使用場(chǎng)景如下圖所示:

InheritedWidget范例示意圖

將最上層的Widget用InheritedWidget包裝起來留夜,并設(shè)置顏色為綠色,子Widget中需要使用該顏色時(shí)調(diào)用FrogColor.of(context).color來獲取图甜,如圖中的Icon和Image碍粥,此時(shí)調(diào)用該方法獲取到的顏色即為綠色。當(dāng)InheritedWidget的數(shù)據(jù)有變化黑毅,即通過setState將綠色改為紅色后嚼摩,依賴了該數(shù)據(jù)的Icon和Image會(huì)自動(dòng)rebuild,構(gòu)造新的Widget時(shí)矿瘦,此時(shí)從FrogColor.of(context).color獲取到的顏色也會(huì)變?yōu)榧t色枕面,由此便實(shí)現(xiàn)了數(shù)據(jù)的同步。

源碼分析

InheritedWidget

先來看下InheritedWidget的源碼:

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

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

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

它繼承自ProxyWidget:

abstract class ProxyWidget extends Widget {
  const ProxyWidget({ Key key, @required this.child }) : super(key: key);

  final Widget child;
}

可以看出Widget內(nèi)除了實(shí)現(xiàn)了createElement方法外沒有其他操作了缚去,它的實(shí)現(xiàn)關(guān)鍵一定就是InheritedElement了潮秘。

InheritedElement

class InheritedElement extends ProxyElement {
  InheritedElement(InheritedWidget widget) : super(widget);

  @override
  InheritedWidget get widget => super.widget;

  // 這個(gè)Set記錄了所有依賴的Element
  final Set<Element> _dependents = new HashSet<Element>();

  // 該方法會(huì)在Element mount和activate方法中調(diào)用,_inheritedWidgets為基類Element中的成員病游,用于提高Widget查找父節(jié)點(diǎn)中的InheritedWidget的效率唇跨,它使用HashMap緩存了該節(jié)點(diǎn)的父節(jié)點(diǎn)中所有相關(guān)的InheritedElement稠通,因此查找的時(shí)間復(fù)雜度為o(1)
  @override
  void _updateInheritance() {
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = new HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

  // 該方法在父類ProxyElement中調(diào)用衬衬,看名字就知道是通知依賴方該進(jìn)行更新了,這里首先會(huì)調(diào)用重寫的updateShouldNotify方法是否需要進(jìn)行更新改橘,然后遍歷_dependents列表并調(diào)用didChangeDependencies方法滋尉,該方法內(nèi)會(huì)調(diào)用mardNeedsBuild,于是在下一幀繪制流程中飞主,對(duì)應(yīng)的Widget就會(huì)進(jìn)行rebuild狮惜,界面也就進(jìn)行了更新
  @override
  void notifyClients(InheritedWidget oldWidget) {
    if (!widget.updateShouldNotify(oldWidget))
      return;
    for (Element dependent in _dependents) {
      dependent.didChangeDependencies();
    }
  }
}

其中_updateInheritance方法在基類Element中的實(shí)現(xiàn)如下:

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

總結(jié)來說就是Element在mount的過程中,如果不是InheritedElement碌识,就簡(jiǎn)單的將緩存指向父節(jié)點(diǎn)的緩存碾篡,如果是InheritedElement,就創(chuàng)建一個(gè)緩存的副本筏餐,然后將自身添加到該副本中开泽,這樣做會(huì)有兩個(gè)值得注意的點(diǎn):

  1. InheritedElement的父節(jié)點(diǎn)們是無法查找到自己的,即InheritedWidget的數(shù)據(jù)只能由父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞魁瞪,反之不能穆律。
  2. 如果某節(jié)點(diǎn)的父節(jié)點(diǎn)有不止一個(gè)同一類型的InheritedWidget惠呼,調(diào)用inheritFromWidgetOfExactType獲取到的是離自身最近的該類型的InheritedWidget。

看到這里似乎還有一個(gè)問題沒有解決峦耘,依賴它的Widget是在何時(shí)被添加到_dependents這個(gè)列表中的呢剔蹋?
回憶一下從InheritedWidget中取數(shù)據(jù)的過程,對(duì)于InheritedWidget有一個(gè)通用的約定就是添加static的of方法辅髓,該方法中通過inheritFromWidgetOfExactType找到parent中對(duì)應(yīng)類型的的InheritedWidget的實(shí)例并返回泣崩,與此同時(shí),也將自己注冊(cè)到了依賴列表中洛口,該方法的實(shí)現(xiàn)位于Element類律想,實(shí)現(xiàn)如下:

@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
  // 這里通過上述mount過程中建立的HashMap緩存找到對(duì)應(yīng)類型的InheritedElement
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
  if (ancestor != null) {
    // 這個(gè)列表記錄了當(dāng)前Element依賴的所有InheritedElement,用于在當(dāng)前Element deactivate時(shí)绍弟,將自己從InheritedElement的_dependents列表中移除技即,避免不必要的更新操作
    _dependencies ??= new HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    // 這里將自己添加到了_dependents列表中,相當(dāng)于注冊(cè)了監(jiān)聽
    ancestor._dependents.add(this);
    return ancestor.widget;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

到此InheritedWidget就分析完了樟遣,相比觀察者模式而叼,使用它是不是方便了很多呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末豹悬,一起剝皮案震驚了整個(gè)濱河市葵陵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞻佛,老刑警劉巖脱篙,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伤柄,居然都是意外死亡绊困,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門适刀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秤朗,“玉大人,你說我怎么就攤上這事笔喉∪∈樱” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵常挚,是天一觀的道長(zhǎng)作谭。 經(jīng)常有香客問我,道長(zhǎng)奄毡,這世上最難降的妖魔是什么折欠? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上怨酝,老公的妹妹穿的比我還像新娘傀缩。我一直安慰自己,他們只是感情好农猬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布赡艰。 她就那樣靜靜地躺著,像睡著了一般斤葱。 火紅的嫁衣襯著肌膚如雪慷垮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天揍堕,我揣著相機(jī)與錄音料身,去河邊找鬼。 笑死衩茸,一個(gè)胖子當(dāng)著我的面吹牛芹血,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播楞慈,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼幔烛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了囊蓝?” 一聲冷哼從身側(cè)響起饿悬,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎聚霜,沒想到半個(gè)月后狡恬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝎宇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年弟劲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夫啊。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡函卒,死狀恐怖辆憔,靈堂內(nèi)的尸體忽然破棺而出撇眯,到底是詐尸還是另有隱情,我是刑警寧澤虱咧,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布熊榛,位于F島的核電站,受9級(jí)特大地震影響腕巡,放射性物質(zhì)發(fā)生泄漏玄坦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煎楣。 院中可真熱鬧豺总,春花似錦、人聲如沸择懂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)困曙。三九已至表伦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慷丽,已是汗流浹背蹦哼。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留要糊,地道東北人纲熏。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像锄俄,于是被迫代替她去往敵國(guó)和親赤套。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 本文介紹了Flutter應(yīng)用程序中Widget珊膜,State容握,BuildContext和InheritedWidge...
    __white閱讀 2,885評(píng)論 1 6
  • 哪有什么完美主義者,完美主義者就是失敗者的另一種飾詞车柠。
    Chrisjiang閱讀 327評(píng)論 0 0
  • 讀假期安全提示后剔氏,我知道了很多的安全常識(shí),如:父母外出不在家時(shí)的居家安全常識(shí)谈跛、假期出去時(shí)游玩的活動(dòng)安全常識(shí)、旅游外...
    _何欣_閱讀 431評(píng)論 0 3
  • 令水成冰 凍住包裹的過往 過往如水 匆忙奔去他鄉(xiāng) 他鄉(xiāng)何方塑陵? 何方感憾? 人云亦云,匆匆過往 冰封凝結(jié)時(shí) 回首 消散如...
    糊涂兔adlen閱讀 181評(píng)論 0 0