Provider的局部刷新机制

以下以Provider 4.0.0版本进行分析。


使用方法就不说了,简单的来说,提供一个数据类型派生自ChangeNotifier,修改数据后调用notifyListeners()进行刷新通知。
有数据刷新需求的Widget外层包裹一个ListenableProvider,构造方法'create'将派生自ChangeNotifier的数据提供出去,'child'就用户自己写的Widget。
通过Provider.of<T>(context)获得数据,进行UI绘制或者修改数据后刷新。最关键的代码就是这一行。

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

⑴:
研究过ListenableProvider的源码可以知道,ListenableProvider build的Widget是_InheritedProviderScope<T>派生自InheritedWidget,提供的element是_InheritedProviderScopeElement<T>.
⑵:
了解Widget的构建后,可以知道这里的context就是调用Provider.of<T>()本身的BuildContext,即一般来说是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代表调用Provider.of<T>的这个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;
}

于是所有调用Provider.of<T>()的widget的Element都被存到了InheritedElement的_dependents.keys中。

⑶:

//_InheritedProviderScopeElement
//这里的value就是给到的数据
T get value => _delegateState.value;

//_CreateInheritedProviderState
T get value{
  ...
  //这个startListening是在ListenableProvider中定义的闭包
  _removeListener ??=delegate.startListening?.call(element, _value);
  ...
}

//ListenableProvider
static VoidCallback _startListening(InheritedContext<Listenable> e, Listenable value){
  //这里的value就是提供的数据,e就是_InheritedProviderScopeElemet
  value? .addListener(e.makeNeedsNotifyDependents);
  return () => value?.removeListener(e.makNeedsNotifyDependents);
}

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

经过上面的第3步,将e.makeNeedsNotifyDependents这个闭包放入了_listeners,从上面的代码也可以看到,只有第一个闭包才会被放入。

现在准备工作都做好了,开始更新数据吧

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

于是,随即调用makeNeedsNotifyDependents()@_InheritedProviderScopeElemet

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

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

在下一个vsync来到时,因为该element被设置为_dirty,因为会进行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();
}

因此,每一次渲染,都会把调用个过Provider.of<T>()的Element保存起来,在下一帧到来的时候进行重新绘制渲染。


Provider提供了一个Selector,可以自定义是否进行rebuild,需要注意的是,如果其父节点进行了build,其必定rebuild,因为使用Selector的时候要特别注意其挂载的节点,否则就丧失了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;
  }
}

其实同理,就可以编写自己的带cache的Widget了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354