Provider的局部刷新機(jī)制

以下以Provider 4.0.0版本進(jìn)行分析攒盈。


使用方法就不說(shuō)了渔工,簡(jiǎn)單的來(lái)說(shuō),提供一個(gè)數(shù)據(jù)類型派生自ChangeNotifier摔癣,修改數(shù)據(jù)后調(diào)用notifyListeners()進(jìn)行刷新通知姑曙。
有數(shù)據(jù)刷新需求的Widget外層包裹一個(gè)ListenableProvider襟交,構(gòu)造方法'create'將派生自ChangeNotifier的數(shù)據(jù)提供出去,'child'就用戶自己寫(xiě)的Widget渣磷。
通過(guò)Provider.of<T>(context)獲得數(shù)據(jù)婿着,進(jìn)行UI繪制或者修改數(shù)據(jù)后刷新。最關(guān)鍵的代碼就是這一行醋界。

//provider.dart
static T of<T>(BuildContext context, {bool listen = true}){
  //⑴竟宋,inheritedElement是_InheritedProviderScopeElement
  final inheritedElement = _inheritedElementOf<T>(context);
  if(listener){
    //⑵
    context.dependOnInheritedElement(inheritedElement);
  }
  //⑶
  return inheritedElement.value;
}

⑴:
研究過(guò)ListenableProvider的源碼可以知道,ListenableProvider build的Widget是_InheritedProviderScope<T>派生自InheritedWidget,提供的element是_InheritedProviderScopeElement<T>.
⑵:
了解Widget的構(gòu)建后形纺,可以知道這里的context就是調(diào)用Provider.of<T>()本身的BuildContext丘侠,即一般來(lái)說(shuō)是StatefulElement/Element,而StatefulElement派生自Element

//StatefulElement
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object aspect}){
  //
  return super.dependOnInheritedElement(ancestor, aspect:aspect);
}
//Element
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object aspect}){
  ...
  //this代表調(diào)用Provider.of<T>的這個(gè)Element逐样,
  ancestor.updateDependencies(this, aspect);
}

//InheritedElement
void updateDependencies(Element dependent, Object aspect){
  setDependencies(dependent, null);
}

final Map<Element, Object> _dependents = HashMap<Element, Object>();
void setDependencies(Element dependent, Object value){
  _dependents[dependent] = value;
}

于是所有調(diào)用Provider.of<T>()的widget的Element都被存到了InheritedElement的_dependents.keys中蜗字。

⑶:

//_InheritedProviderScopeElement
//這里的value就是給到的數(shù)據(jù)
T get value => _delegateState.value;

//_CreateInheritedProviderState
T get value{
  ...
  //這個(gè)startListening是在ListenableProvider中定義的閉包
  _removeListener ??=delegate.startListening?.call(element, _value);
  ...
}

//ListenableProvider
static VoidCallback _startListening(InheritedContext<Listenable> e, Listenable value){
  //這里的value就是提供的數(shù)據(jù),e就是_InheritedProviderScopeElemet
  value? .addListener(e.makeNeedsNotifyDependents);
  return () => value?.removeListener(e.makNeedsNotifyDependents);
}

class ChangeNotifier implements Listenable{
  ObserverList <VoidCallback> _listeners = ObserverList<VoidCallback>();
}

經(jīng)過(guò)上面的第3步脂新,將e.makeNeedsNotifyDependents這個(gè)閉包放入了_listeners,從上面的代碼也可以看到挪捕,只有第一個(gè)閉包才會(huì)被放入。

現(xiàn)在準(zhǔn)備工作都做好了争便,開(kāi)始更新數(shù)據(jù)吧

//ChangeNotifier
void notifyListeners(){
  final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
  for(final VoidCallback listener in localListeners){
    if(_listeners.contains())
      listener();
  }
}

于是级零,隨即調(diào)用makeNeedsNotifyDependents()@_InheritedProviderScopeElemet

//_InheritedProviderScopeElemet
void makeNeedsNotifyDependents(){
  markNeedsBuild();
  _shouldNotifyDependents = true;
}

void markNeedsBuild(){
  _dirty = true;
  owner.scheduleBuildFor(this);
}

在下一個(gè)vsync來(lái)到時(shí),因?yàn)樵揺lement被設(shè)置為_(kāi)dirty滞乙,因?yàn)闀?huì)進(jìn)行build工作

Widget build(){
  if(_shouldNotifyDependents){
    _shouldNotifyDependents = false;
    notifyClients(Widget);
  }
  return super.build();
}

//ProxyElement
Widget build() => widget.child;

void notifyClient(InheritedWidget oldWidget){
  for(final Element dependent in _dependents.key){
    notifyDependent(oldWidget, dependents);
  }
}

void notifyDependent(covariant InheritedWidegt oldWidget, Element dependent){
  dependent.didChangeDependencies(); 
}

void didChangeDependencies(){
  makeNeedsBuild();
}

因此奏纪,每一次渲染鉴嗤,都會(huì)把調(diào)用個(gè)過(guò)Provider.of<T>()的Element保存起來(lái),在下一幀到來(lái)的時(shí)候進(jìn)行重新繪制渲染序调。


Provider提供了一個(gè)Selector醉锅,可以自定義是否進(jìn)行rebuild,需要注意的是发绢,如果其父節(jié)點(diǎn)進(jìn)行了build硬耍,其必定rebuild,因?yàn)槭褂肧elector的時(shí)候要特別注意其掛載的節(jié)點(diǎn)朴摊,否則就喪失了Selector提供的本意默垄。

class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  T value;
  Widget cache;
  Widget oldWidget;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    final selected = widget.selector(context);

    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null &&
            widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null &&
            !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      value = selected;
      oldWidget = widget;
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}

其實(shí)同理此虑,就可以編寫(xiě)自己的帶cache的Widget了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末甚纲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子朦前,更是在濱河造成了極大的恐慌介杆,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韭寸,死亡現(xiàn)場(chǎng)離奇詭異春哨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)恩伺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)赴背,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人晶渠,你說(shuō)我怎么就攤上這事凰荚。” “怎么了褒脯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵便瑟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我番川,道長(zhǎng)到涂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任颁督,我火速辦了婚禮践啄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沉御。我一直安慰自己屿讽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布嚷节。 她就那樣靜靜地躺著聂儒,像睡著了一般虎锚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衩婚,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天窜护,我揣著相機(jī)與錄音,去河邊找鬼非春。 笑死柱徙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奇昙。 我是一名探鬼主播护侮,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼储耐!你這毒婦竟也來(lái)了羊初?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤什湘,失蹤者是張志新(化名)和其女友劉穎长赞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體闽撤,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡得哆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哟旗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贩据。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闸餐,靈堂內(nèi)的尸體忽然破棺而出饱亮,到底是詐尸還是另有隱情,我是刑警寧澤绎巨,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布近尚,位于F島的核電站,受9級(jí)特大地震影響场勤,放射性物質(zhì)發(fā)生泄漏戈锻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一和媳、第九天 我趴在偏房一處隱蔽的房頂上張望格遭。 院中可真熱鬧,春花似錦留瞳、人聲如沸拒迅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)璧微。三九已至作箍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間前硫,已是汗流浹背胞得。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屹电,地道東北人阶剑。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像危号,于是被迫代替她去往敵國(guó)和親牧愁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344