关于Provider,网上的文章太多了,但都比较零散或者偏使用。觉得还是需要深入理解一下,这篇文章算是自己拆解Provider过程的一个记录吧。
Provider 原理解析
打开源码,最先注意到的就是Provider
类。
此Provider
非标题中的Provider
。此处的Provider
顾名思义,只是一个数据的提供者,他只是对InheritedWidget
的一层封装,方便我们对数据的操作。
看下源码~
居然,内容非常之少~
- 继承自
InheritedProvider
- 需要指定一个泛型T
- 一个普通构造器和一个命名构造器
- 一个静态方法
static T of<T>(BuildContext context, {bool listen = true})
,返回指定泛型为T的对象
我们一条一条看。
要认识Provider
,看其父类即可了解三分。InheritedProvider
跟InheritedWidget
有关系?源码注解上写的很直白:A generic implementation of an [InheritedWidget].
果真,是InheritedWidget
的一个通用实现。也就是说Provider
是基于InheritedWidget
原理实现的。对InheritedWidget
不是很了解的,可以简单了解一下这篇【Flutter脱发实录】盘一盘InheritedWidget
因为InheritedWidget
是从下往上,从Element
链表中获取数据的,因此存在当前节点之上多个数据节点的情况。Provider
是通过数据的runtimeType
来区分不同的数据的。而泛型T就限制了数据的类型,以便能准确找到对应的数据。
我们直接贴上代码,逐一分析构造器中的参数。
// 创建数据 T 的Function
typedef Create<T> = T Function(BuildContext context);
// 释放数据 T 的Fuction 可在父类中的State中看到 在State dispose中释放
typedef Dispose<T> = void Function(BuildContext context, T value);
typedef UpdateShouldNotify<T> = bool Function(T previous, T current);
Provider({
Key key,
@required Create<T> create,
Dispose<T> dispose,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : assert(create != null),
super(
key: key,
lazy: lazy,
builder: builder,
create: create,
dispose: dispose,
debugCheckInvalidValueType: kReleaseMode
? null
: (T value) =>
Provider.debugCheckInvalidValueType?.call<T>(value),
child: child,
);
类关系图
首先梳理一下类关系图,能帮助我们更好地学习
Provider左侧是InheritedWidget派系,负责处理数据在Element Tree中传递的逻辑。 Provider右侧是代理派系,主要负责value T 的生命周期管理。
一直没太搞明白一点,代理类分为_Delegate 和 _DelegateState类。_Delegate 的作用是什么,总感觉没用显得很多余?希望大佬点一下!!!谢谢
在构造器中有三个比较有意思的地方,单独分析一下其作用以及原理。
lazy懒加载
这里的lazy
懒加载,指的是在创建Provider
时,并不会去真正创建数据,即构造器中的Create
方法,而是当第一次访问该数据时去创建。当数据的初始化逻辑比较复杂时,可以设置lazy = true
来延迟加载。
原理非常简单,只有一处代码:
class _InheritedProviderScopeElement<T> extends InheritedElement
implements InheritedContext<T> {
@override
Widget build() {
if (widget.owner._lazy == false) {
value; // 当非懒加载是 在build方法中 直接获取 value
}
...
}
}
当设置为非懒加载时,在构建时,即获取value对象,即执行value的生成逻辑,具体可见_CreateInheritedProviderState
中的代码。
当设置为懒加载时,只会在value真实被获取时(Provider.of<T>()、context.read<T>()
等),才会初始化。
builder 模式
builder模式的本质是一个语法糖,提供了一个可用的context
参数,方便我们生成Widget
。
Provider.value()
当value很简单,无需关系它的生命周期时,我们可以使用Provider.value
这个命名函数,更加便捷地创建Provider。
Provider.of <T> ()
Provider唯一暴露的静态方法,作用是向上查找最近的一个使用Provider植入的T类型的value,原理可参考InheritedWidget。
当无法找到对应的value时,会报ProviderNotFoundException异常
Provider 的变种
Provider部分实现的功能仅仅只是最基本的数据插入和获取,显然无法高效地实现业务功能,所以需要对Provider进一步的封装。 作者提供了如下几种变种,能满足大部分的使用场景。
ListenableProvider
继承自InheritedProvider类,做了两件事:
- 指定Listenable作为其泛型
- 默认实现了
startListening
参数
Listenable是Flutter提供的一个发布者-订阅者模式方案,具体可以自行百度,之后也会专门出一篇分析一下。
再看下startListening
做了什么?
static VoidCallback _startListening(
InheritedContext<Listenable> e,
Listenable value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
静态方法提供了默认的实现
- 订阅事件,当接受到事件时,通知Element更新UI
- 返回一个默认取消订阅的回调
这个_startListening在哪里被使用了呢?在_CreateInheritedProviderState这个状态代理类中,在value创建时,注册此订阅事件,并记录回调
T get value {
_removeListener ??= delegate.startListening?.call(element, _value);
}
在dispose方法中,调用回调取消此订阅
@override
void dispose() {
super.dispose();
_removeListener?.call();
if (_didInitValue) {
delegate.dispose?.call(element, _value);
}
}
ChangeNotifierProvider
我们最常用的一种变体,采用ChangeNotifier作为订阅模式。
当在ChangeNotifier类中,调用notifyListeners()
即可通知UI更新。
ProxyProvider
对外暴露了一个update方法,可以帮助我们根据多个不同的value,更新当前持有的value
DeferredInheritedProvider
顾名思义,InheritedProvider的延迟版。子类主要通过创造不同的startListening (VoidCallback)来实现不同的延迟策略。 传入的startListening如下定义:
typedef DeferredStartListening<T, R> = VoidCallback Function(
InheritedContext<R> context,
void Function(R value) setState,
T controller,
R value,
);
由于存在延迟性,创建时并不能及时获取到value,于是提供了setState方法,此setState方法非State中的方法,当传入的异步controller准备完毕时,调用setState方法,更新value值并刷新UI
void setState(R value) {
if (_hasValue) {
final shouldNotify = delegate.updateShouldNotify != null
? delegate.updateShouldNotify(_value, value)
: _value != value;
if (shouldNotify) {
element.markNeedsNotifyDependents();
}
}
_hasValue = true;
_value = value;
}
ValueListenableProvider
采用ValueListenable作为延迟controller。ValueListenable的特性就是关注其持有的value T,一单value发生变化,会自动调用发布订阅,调用addListener中注册的方法。
static DeferredStartListening<ValueListenable<T>, T> _startListening<T>() {
return (_, setState, controller, __) {
setState(controller.value);
// 将setState方法添加到响应中
final listener = () => setState(controller.value);
controller.addListener(listener);
return () => controller.removeListener(listener);
};
}
FutureProvider
使用Future作为延时策略。
需要传入一个T initialData
和ErrorBuilder<T> catchError
错误回调。
DeferredStartListening<Future<T>, T> _futureStartListening<T>({
T initialData,
ErrorBuilder<T> catchError,
}) {
return (e, setState, controller, __) {
if (!e.hasValue) {
setState(initialData);
}
var canceled = false;
controller?.then(
(value) {
if (canceled) return;
setState(value);
},
onError: (dynamic error) {
if (canceled) return;
if (catchError != null) {
setState(catchError(e, error));
}
},
);
return () => canceled = true;
};
}
首先使用初始化value,当Future执行完毕后,setState更新数据,发生异常时,使用catchError进行处理。
StreamProvider
使用Stream作为延时策略。 直接看startlistening的实现:
DeferredStartListening<Stream<T>, T> _streamStartListening<T>({
T initialData,
ErrorBuilder<T> catchError,
}) {
return (e, setState, controller, __) {
if (!e.hasValue) {
setState(initialData);
}
if (controller == null) {
return () {};
}
final sub = controller.listen(
setState,
onError: (dynamic error) {
if (catchError != null) {
setState(catchError(e, error));
}
},
);
return sub.cancel;
};
}
同样在Stream的listen方法中,调用setState方法。
Provider 优化刷新
由于直接使用Provider.of(BuildContext context)
使用的context通常是较外层的,会导致不必要的刷新。Provider提供了两个Widget来优化页面的刷新逻辑:Consumer
和Selector
Consumer
Consumer有两个直接的目的:
- 简化value的获取 - 替代Provider.of
- 提供细粒度的重构来优化页面刷新
这么重要的优化,代码居然如此之简单~
class Consumer<T> extends SingleChildStatelessWidget {
Consumer({
Key key,
@required this.builder,
Widget child,
}) : assert(builder != null),
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,
);
}
}
仅仅只是定义了一个Builder方法就解决了! 看看builder做了哪些事?
- 使用新的context,保证只会刷新Consumer对应的Element之下的节点
- 内部实现对value的获取,直接返回了value
- 允许传入一个Widget作为child,当发生刷新时,child不会刷新。
why???
Selector
使用Consumer时,当value中的一部分发生变化,即便是不关心的那部分,依然会发生重建。于是Selector出现了。 Selector 会摘出value中自己关心的那部分,只有当这部分发生改别时,才会重构。看下是如何实现的
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;
}
}
从源码中可以看到更新条件有如下几个:
oldWidget != widget
这条就不多解释了widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected))
可以自己定义新老数据的比较方法widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected)
默认会用DeepCollectionEquality类来进行比较
那么是如何保证不被重建的呢? Selector采用了缓存cache,当不需要更新时,直接将缓存的Widget返回。
BuildContext 扩展
在Provider中,存在4个对BuildContext的扩展
InheritedContext
InheritedContext是对基本功能的扩展,主要扩展了value方法的获取,最直接的体现就是Provider.of
的实现方法。
static T of<T>(BuildContext context, {bool listen = true}) {
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
由于_InheritedProviderScopeElement
实现了InheritedContext
类,因此可以直接持有value。
SelectContext
通过传入一个selector,允许只关注value的部分内容,类似上文所述的Selector
,但是实现原理缺失不同的,SelectContext是通过InheritedElement中的aspect实现的,具体可参考InheritedWidget
extension SelectContext on BuildContext {
R select<T, R>(R selector(T value)) {
final inheritedElement = Provider._inheritedElementOf<T>(this);
try {
var value = inheritedElement.value;
final selected = selector(value);
dependOnInheritedElement(
inheritedElement,
aspect: (T newValue) => !const DeepCollectionEquality()
.equals(selector(newValue), selected),
);
return selected;
}
}
}
ReadContext
默认了Provider.of<T>(this, listen: false)
,当数据变化时,不会发生重建
WatchContext
简化了Provider.of<T>(this)
的写法
Provider大致就拆解完毕了,写的比较乱。关于具体的使用和Demo,网上有太多资源了,就不贴了。