Flutter状态管理-Provider的使用和源码解析

使用

Provider的使用非常简单,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用来实现状态的管理与Widget的更新。其中ChangeNotifier是系统提供的,用来负责数据的变化通知。ChangeNotifierProvider本质上其实就是Widget,它作为父节点Widget,可将数据共享给其所有子节点Widget使用或更新。具体的原理解析在后续章节会进行说明。所以通常我们只需要三步即可利用Provider来实现状态管理。
1.创建混合或继承ChangeNotifierModel,用来实现数据更新的通知并监听数据的变化。
2.创建ChangeNotifierProvider,用来声明Provider,实现跨组建的数据共享。
3.接收共享数据。
我们来举个例子,看看它是怎么在父子之间进行数据共享的:

例1 Provider的使用:
  • 创建Model
class ProviderViewModel with ChangeNotifier {
  int _number = 0;

  get number => _number;

  void addNumber() {
    _number++;
    notifyListeners();
  }
}

上面的代码很简单,调用addNumber()方法让_number加1,并调用notifyListeners()通知给监听方。

  • 创建ChangeNotifierProvider
class ProviderTestPage extends StatelessWidget {
  final _providerViewModel = ProviderViewModel();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Provider Test"),
      ),
      body: ChangeNotifierProvider.value(
        value: _providerViewModel,
        builder: (context, child) {
          return Column(
            children: [
              const Text("我是父节点"),
              Text(
                  "Parent number is: ${Provider.of<ProviderViewModel>(context).number}"),
              ChildA(),
            //ChildB(),
            //ChildC()
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          _providerViewModel.addNumber();
        }, //使用context.read不会调用rebuild
      ),
    );
  }
}

我们用ChangeNotifierProvider将父布局包裹,在父或子节点ChildA通过Provider.of<T>(BuildContext context, {bool listen = true})进行数据操作,可同步更新父与子的数据与UI。其中listen默认为true可监听数据的变化,为false的情况只可读取数据的值。

  • 接收共享数据:
class ChildA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childA build");
    return Container(
      width: double.infinity,
      color: Colors.amberAccent,
      child: Column(
        children: [
          Text(
              "Child A number: ${Provider.of<ProviderViewModel>(context).number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                Provider.of<ProviderViewModel>(context, listen: false)
                    .addNumber();
              })
        ],
      ),
    );
  }
}

来看一下效果:


例1

我们可以看到不管是在父节点还是在子节点,都可以对ProviderViewModel的数据进行操作和监听。例1在操作与读取时使用的是Provider.of<T>(BuildContext context, {bool listen = true})的方式,为了可以更明确对于Provider的操作,我们可将它替换为context.watch<>()和context.read<>()方式。 我们可以通过源码看到,context.watch<>()context.read<>()方法其实都是调用Provider.of<T>(BuildContext context, {bool listen = true})来实现的:


T watch<T>() {
    return Provider.of<T>(this);
  }
 
T read<T>() {
    return Provider.of<T>(this, listen: false);
  }

语义更加清晰明确。 如:

class ChildB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childB build");
    return Container(
      width: double.infinity,
      color: Colors.red,
      child: Column(
        children: [
          const Text("我是子节点"),
          Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                context.read<ProviderViewModel>().addNumber();
              })
        ],
      ),
    );
  }
}

ChildBChildA实际上是一致的。我们把ProviderTestPageChildB()放开:

image.png

其中,每点击一次父Widget右下角的加号或子Widget的Add Number按钮,我们看一下Log打印的结果:

flutter: childA build
flutter: child build

我们会发现每一次的操作,都会导致ChildAChildB整体重新build。但实际上从代码中我们可知,在ChildAChildB中,只有以下的Text()会监听ProviderViewModel的数据更新:

//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")
 
//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().number}")

那么我们希望可以实现局部的更新该如何实现?Flutter提供了Consumer<>()来进行支持。下面我们来看一下Consumer<>()的用法:

class ChildC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childC build");
    return Container(
      width: double.infinity,
      color: Colors.blue,
      child: Column(
        children: [
          const Text("我是子节点"),
          Consumer<ProviderViewModel>(builder: (context, value, child) {
            print("ChildC Consumer builder");
            return Text("Child C number: ${value.number}");
          }),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                context.read<ProviderViewModel>().addNumber();
              })
        ],
      ),
    );
  }
}

由于我们只希望Text()来监听ProviderViewModel的数据更新,我们用Consumer<>()包裹住Text(),其中builder的传参value即是ProviderViewModel对象,把ProviderTestPageChildC()放开,我们看一下结果:

image.png

再打印一下Log

flutter: child build
flutter: childA build
flutter: Child Consumer builder

Log中我们可以得知,ChildC并没有被rebuild,而是由Consumer调用内部的builder来实现局部更新的。 到此为止,一个简单的Provider使用就介绍完成。另外Provider还提供了ProxyProvider,从名字上来看,我们可知这是个代理Provider,它是用来协调ModelModel之间的更新,比如一个ModelA依赖另一个ModelBModelB更新,他就要让依赖它的ModelA也随之更新。我们直接上代码来看一下它的用法,还是分三步:

例2 ProxyProvider的使用:
  • 创建ProxyProviderViewModel
class ProxyProviderViewModel with ChangeNotifier {
  int number;
 
  ProxyProviderViewModel(this.number);
 
  String get title {
    return "The number is: $number";
  }
}

这个类只是简单的在构造方法例传入int值,并创建get方法得到一段文本。

  • 创建Provider:
class ProxyProviderTestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Provider Test"),
      ),
      body: MultiProvider(
        providers: [
          ChangeNotifierProvider<ProviderViewModel>(
              create: (_) => ProviderViewModel()),
          ChangeNotifierProxyProvider<ProviderViewModel,
                  ProxyProviderViewModel>(
              create: (context) => ProxyProviderViewModel(
                  context.read<ProviderViewModel>().number),
              update: (context, providerViewModel, proxyProviderViewModel) =>
                  ProxyProviderViewModel(providerViewModel.number))
        ],
        builder: (context, child){
          return Column(
            children: [
              ChildProxy(),
              MaterialButton(
                  child: const Text("Add Number"),
                  color: Colors.amberAccent,
                  onPressed: () {
                    context.read<ProviderViewModel>().addNumber();
                  })
            ],
          );
        },
      ),
    );
  }
}

我们在body中用MultiProvider来包裹布局。MultiProvider的作用是同时可声明多个Provider供使用,为参数providers添加Provider数组。我们首先声明一个ChangeNotifierProvider,同例1中的ProviderViewModel。接着我们声明一个ChangeNotifierProxyProvider用来做代理Provider。其中create参数是ProxyProvider的创建,update参数是ProxyProvider的更新。在我们的例子中,实际上是对ProviderViewModel进行数据操作,由ProxyProviderViewModel监听ProviderViewModel的数据变化。

  • 接收共享数据:
class ChildProxy extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      child: Column(
        children: [
          Text(context.watch<ProxyProviderViewModel>().title),
        ],
      ),
    );
  }
}

ChildProxy中,我们监听ProxyProviderViewModel的数据变化,并将title显示在Text()中,进行UI上的更新。 我们看一下效果:

image.png

我们调用context.read<ProviderViewModel>().addNumber()ProviderViewModel的数据进行更新,可同时更新ProxyProviderViewModelnumber对象,而ChildProxy由于监听了ProxyProviderViewModel的数据变化,会因此更新UI中title的内容。
好了,到此为止,Provider的使用介绍到这里,下面我们将针对Provider的原理进行说明。Provider实际上是对InheritedWidget进行了封装,它才是真正实现父与子数据共享的重要元素,所以为了理清Provider的原理,我们必须先弄清楚InheritedWidget的实现过程。

InheritedWidget的原理及解析

InheritedWidget提供了沿树向下,共享数据的功能,系统中的ProviderTheme等实现都是依赖于它。弄清楚它的原理,对于理解Flutter的数据共享方式会有很大的帮助。本章节将先通过实例说明InheritedWidget的用法,然后进行原理的解析。

使用

我们举个简单的例子:


image.png

这是一个简单的树结构,其中ChildAChildC需要共享Data这个数据,ChildB不需要。传递方式有很多种,比如说通过构造方法传递,通过函数调用,函数回调传递等。但是如果树层级非常多的话,刚才提到的传递方式将会对整个代码结构带来灾难,包括代码耦合度过高,回调地狱等。我们看看InheritedWidget是怎么处理的:
1.作为整个树的父节点,需要使ChildA继承InheritedWidget

class ChildA extends InheritedWidget {
  int number;
 
  ChildA({required Widget child, required this.number}) : super(child: child);
 
  static ChildA? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ChildA>();
  }
 
  @override
  bool updateShouldNotify(covariant ChildA oldWidget) {
    return oldWidget.number != number;
  }
}
  • 其中updateShouldNotify()方法需要被重写,用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件。
  • of()方法是一种约定俗成的通用写法,只是起到方便调用的作用。其中context.dependOnInheritedWidgetOfExactType()是为它的子组件注册了依赖关系。 通过这样的方式,将ChildA声明成了一个给子组件共享数据的Widget
    ChildB就是一个中间层级的普通Widget,用来连接树结构:
class ChildB extends StatefulWidget {
  final Widget child;
 
  ChildB({Key? key, required this.child}) : super(key: key);
 
  @override
  _ChildBState createState() => _ChildBState();
}
 
class _ChildBState extends State<ChildB> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("ChildB didChangeDependencies");
  }
 
  @override
  Widget build(BuildContext context) {
    print("ChildB build");
    return Container(
      width: double.infinity,
      color: Colors.amberAccent,
      child: Column(
        children: [const Text("我是子节点 ChildB"), widget.child],
      ),
    );
  }
}

ChildC依赖ChildA,并读取ChildA的共享数据,代码如下:

class ChildC extends StatefulWidget {
  ChildC({Key? key}) : super(key: key);
 
  @override
  _ChildCState createState() => _ChildCState();
}
 
class _ChildCState extends State<ChildC> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("ChildC didChangeDependencies");
  }
 
  @override
  Widget build(BuildContext context) {
    print("ChildC build");
    return Container(
      width: double.infinity,
      color: Colors.red,
      child: Column(
        children: [
          const Text("我是子节点 ChildC"),
          Text("Child C number: ${ChildA.of(context)?.number}"),
        ],
      ),
    );
  }
}

ChildC通过ChildA.of(context)?.number的方式读取ChildA的共享数据。 我们把这个树串起来:

class InheritedWidgetTestPage extends StatefulWidget {
  InheritedWidgetTestPage({Key? key}) : super(key: key);
 
  @override
  _InheritedWidgetTestPageState createState() =>
      _InheritedWidgetTestPageState();
}
 
class _InheritedWidgetTestPageState extends State<InheritedWidgetTestPage> {
  int _number = 10;
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("InheritedWidget Test"),
      ),
      body: ChildA(
        number: _number,
        child: ChildB(
          child: ChildC(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _number++;
          });
        },
      ),
    );
  }
}

在点击floatingActionButton的时候,修改_number的值,通过构造方法传递给ChildA,整个树重新buildChildC读取ChildA的数据,并进行UI的更新,我们看一下结果:

image.png

ChildC接收到了数据变化并进行了更新,我们再来看一下Log

flutter: ChildB build
flutter: Child didChangeDependencies
flutter: Child build

在这个过程中,会发现ChildBChildC都进行了rebuild,由于ChildC依赖ChildA的共享数据,ChildCrebuild之前执行了didChangeDependencies()方法,说明ChildC的依赖关系发生了改变;而ChildB由于不依赖ChildA的共享数据所以并没有执行didChangeDependencies()
这个例子给出了InheritedWidget的一个基本使用方式,但需要注意的是,在整个树结构中,其实ChildB是不依赖ChildA的共享数据的,按理来说,在数据发生变化,我们是不希望ChildB进行rebuild的。所以需要说明的是,InheritedWidget的正确用法并不是通过setState()来实现rebuild的,这里用setState()举例仅仅是为了将整个流程串起来。这个例子的重点在于,依赖父组件的共享数据的子组件,将在生命周期中执行didChangeDependencies()方法。我们可以通过ValueNotifier+ValueListenable来进行局部的更新,这部分出离了本文的内容,先不作展开。
接下来我们分析一下InheritedWidget是如何实现父与子之间的数据共享的。

原理及解析

为了实现父与子的数据共享,我们需要弄清楚两件事:

  • 父绑定子的方式
  • 父通知子的方式
父绑定子的方式

我们先来看一下InheritedWidget这个类:

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key? key, required Widget child })
    : super(key: key, child: child);
 
  @override
  InheritedElement createElement() => InheritedElement(this);
 
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

InheritedWidget继承ProxyWidget,最终继承的是Widget。它只有两个方法,一个是updateShouldNotify(),在上面的例子中可知是用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件的。另外还重写了createElement()方法,创建一个InheritedElement对象。InheritedElement最终继承Element,我们先看一下它的结构:

image.png

从命名中我们就可知setDependencies()是用来绑定依赖关系的。接下来我们从子组件获取InheritedWidget实例开始看起,看看具体的绑定流程。如实例中的如下代码:

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

我们看一下context.dependOnInheritedWidgetOfExactType<ChildA>()的流程:

//BuildContext
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
//Element
  @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;
  }

真正的实现是在Element中进行的。其中_inheritedWidgets是个MapkeyT的类型。从上面代码我们可以知道,先从_inheritedWidgets里寻找类型为TInheritedElement,即父的InheritedElement_updateInheritance()是在mount()activate()调用的,_inheritedWidgets的初始化在子类InheritedElement_updateInheritance()中的实现如下:

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

Element中的实现如下:

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

从上面的代码我们可以得知,普通Element组件在生命周期的初始阶段,它的_inheritedWidgets为父组件的_inheritedWidgets。而_inheritedWidgetsInheritedElement,会将自己添加到_inheritedWidgets中,从而通过此方式将组件和InheritedWidgets的依赖关系层层向下传递,每一个Element中都含有_inheritedWidgets集合,此集合中包含了此组件的父组件且是InheritedWidgets组件的引用关系。接下来我们回到dependOnInheritedWidgetOfExactType()方法,ancestor已经被加到_inheritedWidgets中,所以它不为空,我们继续看里面dependOnInheritedElement()的实现:

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

好到此为止,我们证实了之前的猜测,子组件找到InheritedElement类型的父组件,父组件调用setDependencies(),为子组件向_dependents中添加注册,InheritedWidget组件更新时可以根据此列表通知子组件。将以上过程总结一下,如下图:

image.png

  • 父组件在InheritedElement的初始阶段:mount()activate()的时候调用_updateInheritance()方法将自己添加到_inheritedWidgets中。其他Element子组件会直接拿父组件的_inheritedWidgets
  • 子组件在调用context.dependOnInheritedWidgetOfExactType<>()时,将自己注册给_inheritedWidgets中获取的InheritedElement类型的父组件的dependents中,从而实现了依赖关系的确定。
    接下来我们看一下当组件发生变化时,父通知子的方式。
父通知子的方式

在实例中,当setState()发生数据改变的时候,经过一系列处理后,会走到InheritedElement的updated()方法中去:

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

当执行了我们自定义InheritedWidgetupdateShouldNotify()判断现有共享数据和旧的共享数据是否一致需要更新后,继续执行父类ProxyElement的updated()方法:

//ProxyElement
@protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
//InheritedElement
  @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);
    }
  }

从这段代码中我们可以看出,在notifyClients()中会对_dependentskey进行遍历,然后执行notifyDependent()进行通知。接着我们看notifyDependent()都做了什么:

@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}
@mustCallSuper
void didChangeDependencies() {
  assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}

它调用了每个dependentdidChangeDependencies()方法,来通知InheritedWidget依赖发生了变化,当前element需要被标记为dirty,重新进行build。到此为止,完成了当数据发生变化时,父通知子的流程。我们看一下父通知子的流程图:

image.png

总结一下就是当InheritedElement数据发生变化而更新的时候,父InheritedWidget会遍历_dependents,子会执行didChangeDependencies()方法将子组件标记为dirty而重新build
了解了InheritedWidget的实现后,我们下个章节对Provider进行解析。

Provider解析

接下来我们分析一下Provider的实现。还记着文章开头,我们说明需要三步来利用Provider来实现状态管理。
1.创建混合或继承ChangeNotifierModel,用来实现数据更新的通知并监听数据的变化。
2.创建ChangeNotifierProvider,用来声明Provider,实现跨组建的数据共享。
3.接收共享数据。
我们从创建Model开始讲起:

ChangeNotifier

ChangeNotifier实现了Listenable接口,而Listenable实际上就是一个观察者模型。我们先来看一下ChangeNotifier的结构:

image.png

ChangeNotifier里维护了一个_listeners对象,通过addListener()removeListener()进行添加或删除。在调用notifyListeners()的时候将数据通知给_listenersChangeNotifier非常简单,我们接着来分析ChangeNotifierProvider的实现。

ChangeNotifierProvider

ChangeNotifierProvider继承了多个层级:ListenableProvider->InheritedProvider->SingleChildStatelessWidget->StatelessWidget,实际上它是个StatelessWidget。我们从ChangeNotifierProvider.value()方法开始:

//ChangeNotifierProvider
  ChangeNotifierProvider.value({
    Key? key,
    required T value,
    TransitionBuilder? builder,
    Widget? child,
  }) : super.value(
          key: key,
          builder: builder,
          value: value,
          child: child,
        );

其中required T valueChangeNotifier对象,我们继续看super()的调用:


//ListenableProvider
  ListenableProvider.value({
    Key? key,
    required T value,
    UpdateShouldNotify<T>? updateShouldNotify,
    TransitionBuilder? builder,
    Widget? child,
  }) : super.value(
          key: key,
          builder: builder,
          value: value,
          updateShouldNotify: updateShouldNotify,
          startListening: _startListening,
          child: child,
        );
 
  static VoidCallback _startListening(
    InheritedContext e,
    Listenable? value,
  ) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }

ListenableProvider创建了一个VoidCallback对象,其中value是个Listenable对象,就是我们传入的ChangeNotifier对象。它的实现是为ChangeNotifier添加listener,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法,这个我们之后再做讨论。总而言之,ListenableProvider的作用就是帮我们为ChangeNotifier添加了listener。我们接着往下看:

//InheritedProvider
  InheritedProvider.value({
    Key? key,
    required T value,
    UpdateShouldNotify<T>? updateShouldNotify,
    StartListening<T>? startListening,
    bool? lazy,
    this.builder,
    Widget? child,
  })
      : _lazy = lazy,
        _delegate = _ValueInheritedProvider(
          value: value,
          updateShouldNotify: updateShouldNotify,
          startListening: startListening,
        ),
        super(key: key, child: child);

到了InheritedProvider这一层,我们发现builder没有被继续传下去了,InheritedProvider持有了一个_ValueInheritedProvider类型的_delegate。它的父类_Delegate的代码如下:

abstract class _Delegate<T> {
  _DelegateState<T, _Delegate<T>> createState();
 
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

看到有个看上去跟状态相关的方法需要重写:createState(),我们继续看一下_ValueInheritedProvider重写的createState()的实现:

@override
_ValueInheritedProviderState<T> createState() {
  return _ValueInheritedProviderState<T>();
}

返回了_ValueInheritedProviderState对象,_ValueInheritedProviderState继承了_DelegateState,而_DelegateState持有了一个_InheritedProviderScopeElement对象。继续看一下_ValueInheritedProviderState的结构:

image.png

它定义了willUpdateDelegate()dispose()这两个方法,用来做更新和注销。这么看来_ValueInheritedProviderState这个类实际上是个状态的代理类,类似StatefulWidgetState的关系。我们一开始提到其实ChangeNotifierProvider是个StatelessWidget,那么它的状态肯定是由其他类代理的,由此可知,ChangeNotifierProvider的状态是由_ValueInheritedProviderState来代理。
ChangeNotifierProvider对于Widget的实现实际上是在父类InheritedProvider进行的,我们看一下InheritedProvider的结构:

image.png

终于看到了buildWithChild()这个方法,这是真正我们想看的Widget的内部结构的创建:

@override
Widget buildWithChild(BuildContext context, Widget? child) {
  assert(
  builder != null || child != null,
  '$runtimeType used outside of MultiProvider must specify a child',
  );
  return _InheritedProviderScope<T>(
    owner: this,
    // ignore: no_runtimetype_tostring
    debugType: kDebugMode ? '$runtimeType' : '',
    child: builder != null
        ? Builder(
      builder: (context) => builder!(context, child),
    )
        : child!,
  );
}

我们看到我们所创建的builderchild实际上是被_InheritedProviderScope()进行了包裹。我们继续分析_InheritedProviderScope

class _InheritedProviderScope<T> extends InheritedWidget {
  const _InheritedProviderScope({
    required this.owner,
    required this.debugType,
    required Widget child,
  }) : super(child: child);
 
  final InheritedProvider<T> owner;
  final String debugType;
 
  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }
 
  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}

到这我们终于看到_InheritedProviderScope继承了我们熟悉的InheritedWidget,说明我们的创建的Widget都是被InheritedWidget进行了包裹。在createElement()时返回了_InheritedProviderScopeElement对象。_InheritedProviderScopeElement继承InheritedElement,并实现了InheritedContext接口。我们先看一下它的结构:

image.png

首先我们关注到有个_delegateState的变量,对应的就是我们上面所提到的_ValueInheritedProvider,看一下它初始化的位置:

void performRebuild() {
  if (_firstBuild) {
    _firstBuild = false;
    _delegateState = widget.owner._delegate.createState()
      ..element = this;
  }
  super.performRebuild();
}

performRebuild的时候,调用widget_delegate对象的createState()方法,即_ValueInheritedProvider的createState()方法,得到一个_ValueInheritedProviderState对象。并将自己赋值给_ValueInheritedProviderStateelement对象。 还记不记着在讲ListenableProvider的时候提到它添加了listener,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法,而markNeedsNotifyDependents()的定义就在_InheritedProviderScope里:

@override
void markNeedsNotifyDependents() {
  if (!_isNotifyDependentsEnabled) {
    return;
  }
 
  markNeedsBuild();
  _shouldNotifyDependents = true;
}

这里我看看到它将_InheritedProviderScopeElement标志为markNeedsBuild(),即需要被rebuild的组件,然后将_shouldNotifyDependents标志为true
回到我们的ChangeNotifier:当我们调用notifyListeners()来通知数据变化的时候,如果有listener被注册,实际上会执行InheritedContext.markNeedsNotifyDependents()方法,具体会执行到的位置在ChangeNotifierProvider组件的父类InheritedProvider包裹的_InheritedProviderScope这个InheritedWidget对应的_InheritedProviderScopeElementmarkNeedsNotifyDependents()方法。
整个过程可总结为下图:

image.png

不过到目前为止,我们只是知道了这个流程,但是listener什么时候被注册,子组件又是如何被刷新的呢?我们继续从实例中的Provider.of<>()分析起。

Provider.of<>()

在取数据的时候,如实例代码:Provider.of<ProviderViewModel>(context).number;,来看of()方法的实现:

static T of<T>(BuildContext context, {bool listen = true}) {
  assert(
    context.owner!.debugBuilding ||
        listen == false ||
        debugIsInInheritedProviderUpdate,
  );
 
  final inheritedElement = _inheritedElementOf<T>(context);
 
  if (listen) {
    context.dependOnInheritedElement(inheritedElement);
  }
  return inheritedElement.value;
}

首先获取context_InheritedProviderScopeElement对象,然后调用context.dependOnInheritedElement()方法。这个方法我们很熟悉了,在上个章节介绍InheritedWidget的时候了解过,作用是让子组件找到InheritedElement类型的父组件,父组件调用setDependencies(),为子组件向_dependents中添加注册以形成依赖关系,InheritedWidget组件更新时可以根据此列表通知子组件。接着调用inheritedElement.value返回一个ChangeNotifier对象。这个就是之前我们在ChangeNotifierProvider.value()过程中传入的ChangeNotifier对象。那么在取值的过程中还做了些什么呢?我们继续分析:

@override
T get value => _delegateState.value;

它实际上取的是_ValueInheritedProviderStatevalue

@override
T get value {
  element!._isNotifyDependentsEnabled = false;
  _removeListener ??= delegate.startListening?.call(element!, delegate.value);
  element!._isNotifyDependentsEnabled = true;
  assert(delegate.startListening == null || _removeListener != null);
  return delegate.value;
}

从这段代码中,我们看到了,在取值的过程中,调用了delegate.startListening?.call(element!, delegate.value),为上一节所提到的listener进行了注册。意味着当notifyListeners()时,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法。我们还记着在分析InheritedWidget的时候说明父通知子的时候,会调用InheritedElement的notifyDependent()方法,那么在Provider中,会在其子类_InheritedProviderScopeElement进行实现,代码如下:

@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
  final dependencies = getDependencies(dependent);
 
  if (kDebugMode) {
    ProviderBinding.debugInstance.providerDidChange(_debugId);
  }
 
  var shouldNotify = false;
  if (dependencies != null) {
    if (dependencies is _Dependency<T>) {
      if (dependent.dirty) {
        return;
      }
 
      for (final updateShouldNotify in dependencies.selectors) {
        try {
          assert(() {
            _debugIsSelecting = true;
            return true;
          }());
          shouldNotify = updateShouldNotify(value);
        } finally {
          assert(() {
            _debugIsSelecting = false;
            return true;
          }());
        }
        if (shouldNotify) {
          break;
        }
      }
    } else {
      shouldNotify = true;
    }
  }
 
  if (shouldNotify) {
    dependent.didChangeDependencies();
  }
}

先取shouldNotify的值,由于我们没有用selector,这时候shouldNotifytrue,当前Widget将会进行rebuild。那么如果我们并没有用Consumer,这时候的Provider.of<ProviderViewModel>(context)context实际上是ChangeNotifierProvider对应的context,整个ChangeNotifierProvider都会进行rebuild操作。Consumer的局部更新如何实现的呢?

Consumer

其实这个很简单,看一下Consumer的实现:

class Consumer<T> extends SingleChildStatelessWidget {
  /// {@template provider.consumer.constructor}
  /// Consumes a [Provider<T>]
  /// {@endtemplate}
  Consumer({
    Key? key,
    required this.builder,
    Widget? child,
  }) : super(key: key, child: child);
 
 
  final Widget Function(
    BuildContext context,
    T value,
    Widget? child,
  ) builder;
 
  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }
}

buildWithChild()中实际上也是使用了Provider.of<T>(context),不过要注意的是,这个context是当前组件的context,所以最终只有被Consumer包裹住的子组件才会向_dependents中添加注册以形成依赖关系,才会被标记为dirty从而进行rebuild
好了到此为止,Provider的解析已经完成了,总结一下:

  • Provider实际上是个无状态的StatelessWidget,通过包装了InheritedWidget实现父子组件的数据共享,通过自定义InheritedElement实现刷新。
  • Provider通过与ChangeNotifier配合使用,实现了观察者模式,Provider会将子组件添加到父组件的依赖关系中,在notifyListeners()时,会执行InheritedContext.markNeedsNotifyDependents(),将组件标记为dirty等待重绘。
  • Consumer会只将被它包裹住的子组件注册给父的_dependents形成依赖关系,从而实现了局部更新。
    下面我们看一下几种在Flutter中比较流行的状态同步框架并进行比较。
几种状态同步框架的对比和选择
image.png

这几个状态同步框架,包括其衍生的一些框架的核心原理都是利用了InheritedWidget实现的。虽然Google官方推荐的使用Provider,但在开发过程中需要根据项目大小,开发人员习惯等因素去考虑。
转载自(https://mp.weixin.qq.com/s/vUhDvHaStrTwbE4tovOqtA)。

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

推荐阅读更多精彩内容