好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

前言

在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。 Flutter 作为跨平台框架的后起之秀,背靠 Google 大树,短时间内开发者们在开源社区提供了多种状态管理框架。而 Provider 是官方推荐的状态管理方式之一,可用作跨组件的数据共享。本文将针对 Provider 框架的使用及实现原理作详细的说明,并在最后对主流的状态管理框架进行比较。

使用

Provider 的使用非常简单,通常使用 ChangeNotifierProvider 配合 ChangeNotifier 一起使用来实现状态的管理与Widget的更新。其中 ChangeNotifier 是系统提供的,用来负责数据的变化通知。 ChangeNotifierProvider 本质上其实就是 Widget ,它作为父节点 Widget ,可将数据共享给其所有子节点 Widget 使用或更新。具体的原理解析在后续章节会进行说明。所以通常我们只需要三步即可利用 Provider 来实现状态管理。

1.创建混合或继承 ChangeNotifier 的 Model ,用来实现数据更新的通知并监听数据的变化。

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();
              })
        ],
      ),
    );
  }
} 

我们来看一下效果: 我们可以看到不管是在父节点还是在子节点,都可以对 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();
              })
        ],
      ),
    );
  }
} 

ChildB 与 ChildA 实际上是一致的。我们把 ProviderTestPage 的 ChildB() 放开: 其中,每点击一次父 Widget 右下角的加号或子 Widget 的Add Number按钮,我们看一下 Log 打印的结果: 我们会发现每一次的操作,都会导致 ChildA 与 ChildB 整体重新 build 。但实际上从代码中我们可知,在 ChildA 和 ChildB 中,只有以下的 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 对象,把 ProviderTestPage 的 ChildC() 放开,我们看一下结果: 再打印一下 Log :

从 Log 中我们可以得知, ChildC 并没有被 rebuild ,而是由 Consumer 调用内部的 builder 来实现局部更新的。 到此为止,一个简单的 Provider 使用就介绍完成。另外 Provider 还提供了 ProxyProvider ,从名字上来看,我们可知这是个代理Provider,它是用来协调Model与Model之间的更新,比如一个ModelA依赖另一个ModelB,ModelB更新,他就要让依赖它的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上的更新。 我们看一下效果:

我们调用 context.read<ProviderViewModel>().addNumber() 对 ProviderViewModel 的数据进行更新,可同时更新 ProxyProviderViewModel 的 number 对象,而 ChildProxy 由于监听了 ProxyProviderViewModel 的数据变化,会因此更新UI中 title 的内容。

好了,到此为止, Provider 的使用介绍到这里,下面我们将针对 Provider 的原理进行说明。 Provider 实际上是对 InheritedWidget 进行了封装,它才是真正实现父与子数据共享的重要元素,所以为了理清 Provider 的原理,我们必须先弄清楚 InheritedWidget 的实现过程。

InheritedWidget的原理及解析

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

使用

我们举个简单的例子:

这是一个简单的树结构,其中 ChildA 和 ChildC 需要共享 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 ,整个树重新 build 。 ChildC 读取 ChildA 的数据,并进行UI的更新,我们看一下结果:

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

在这个过程中,会发现 ChildB 和 ChildC 都进行了 rebuild ,由于 ChildC 依赖 ChildA 的共享数据, ChildC 在 rebuild 之前执行了 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 ,我们先看一下它的结构: 从命名中我们就可知 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 是个 Map , key 为 T 的类型。从上面代码我们可以知道,先从 _inheritedWidgets 里寻找类型为 T 的 InheritedElement ,即父的 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 。而 _inheritedWidgets 的 InheritedElement ,会将自己添加到 _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 组件更新时可以根据此列表通知子组件。将以上过程总结一下,如下图:

父组件在 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);
} 

当执行了我们自定义 InheritedWidget 的 updateShouldNotify() 判断现有共享数据和旧的共享数据是否一致需要更新后,继续执行父类 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() 中会对 _dependents 的 key 进行遍历,然后执行 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();
} 

它调用了每个 dependent 的 didChangeDependencies() 方法,来通知 InheritedWidget 依赖发生了变化,当前 element 需要被标记为 dirty ,重新进行 build 。到此为止,完成了当数据发生变化时,父通知子的流程。我们看一下父通知子的流程图: 总结一下就是当 InheritedElement 数据发生变化而更新的时候,父 InheritedWidget 会遍历 _dependents ,子会执行 didChangeDependencies() 方法将子组件标记为 dirty 而重新 build 。

了解了 InheritedWidget 的实现后,我们下个章节对 Provider 进行解析。

Provider解析

接下来我们分析一下 Provider 的实现。还记着文章开头,我们说明需要三步来利用 Provider 来实现状态管理。

1.创建混合或继承 ChangeNotifier 的 Model ,用来实现数据更新的通知并监听数据的变化。

2.创建 ChangeNotifierProvider ,用来声明 Provider ,实现跨组建的数据共享。

3.接收共享数据。

我们从创建 Model 开始讲起:

ChangeNotifier

ChangeNotifier 实现了 Listenable 接口,而 Listenable 实际上就是一个观察者模型。我们先来看一下 ChangeNotifier 的结构: 在 ChangeNotifier 里维护了一个 _listeners 对象,通过 addListener() 和 removeListener() 进行添加或删除。在调用 notifyListeners() 的时候将数据通知给 _listeners 。 ChangeNotifier 非常简单,我们接着来分析 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 value 是 ChangeNotifier 对象,我们继续看 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 的结构: 它定义了 willUpdateDelegate() 和 dispose() 这两个方法,用来做更新和注销。这么看来 _ValueInheritedProviderState 这个类实际上是个状态的代理类,类似 StatefulWidget 和 State 的关系。我们一开始提到其实 ChangeNotifierProvider 是个 StatelessWidget ,那么它的状态肯定是由其他类代理的,由此可知, ChangeNotifierProvider 的状态是由 _ValueInheritedProviderState 来代理。

ChangeNotifierProvider 对于 Widget 的实现实际上是在父类 InheritedProvider 进行的,我们看一下 InheritedProvider 的结构: 终于看到了 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!,
  );
} 

我们看到我们所创建的 builder 或 child 实际上是被 _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 接口。我们先看一下它的结构: 首先我们关注到有个 _delegateState 的变量,对应的就是我们上面所提到的 _ValueInheritedProvider ,看一下它初始化的位置:

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

在 performRebuild 的时候,调用 widget 的 _delegate 对象的 createState() 方法,即 _ValueInheritedProvider 的 createState() 方法,得到一个 _ValueInheritedProviderState 对象。并将自己赋值给 _ValueInheritedProviderState 的 element 对象。 还记不记着在讲 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 对应的 _InheritedProviderScopeElement 的 markNeedsNotifyDependents() 方法。

整个过程可总结为下图: 不过到目前为止,我们只是知道了这个流程,但是 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; 

它实际上取的是 _ValueInheritedProviderState 的 value :

 @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 ,这时候 shouldNotify 为 true ,当前 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 中比较流行的状态同步框架并进行比较。

几种状态同步框架的对比和选择

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

总结

本文对 Provider 框架的使用及实现原理作详细的说明,为了能够更好的进行理解,也对 InheritedWidget 的实现进行了详细的说明,并在最后对主流的状态管理框架进行比较。希望能帮助大家更好的理解 Flutter 的数据共享机制,并根据自身需求选择合适的框架应用到实际项目中。

参考文献

https://www.cnblogs.com/mengqd/p/14300373.html

https://www.jianshu.com/p/bf2f33b2b5ef

查看更多关于Flutter状态管理-Provider的使用和源码解析的详细内容...

  阅读:60次