Flutter状态管理ScopedModel源码分析
Scoped Model是什么
不管你是Android开发还是iOS开发,都涉及到状态管理,Flutter开发的状态管理框架主要有
Redux
Bloc
Scoped Model
本博主要对Scoped Model的源码进行分析,Scoped Model是dart第三方开源库,它能让你无视Widget层级的深度,直接访问父Widget乃至祖先Widget的数据模型,并且在数据模型发生变化时重新渲染访问该模型的所有子Widget。
在分析源码之前我们先想一想,如果我们自己开发一个状态管理框架,如耳熟能详的MVVM框架,模型变化时要通知模型访问者,很自然的就想到了设计模式中的观察者模式,Scoped Model框架设计思想也不例外,首先我们来看看Scoped Model框架怎么使用的
Scoped Model的使用
首先在pubspec.yaml文件加入
dependencies:
scoped_model: 1.0.1
packages get后就可以使用了
@override
Widget build(BuildContext context) {
super.build(context);
return ScopedModel(
model: _vm,
child: ScopedModelDescendant<HomePageViewModel>(
builder: (BuildContext content, _, __) {
return _buildContentList();
},
),
);
}
首次或模型变化时就会调用builder重新渲染页面,_vm 表示的是Model 状态管理类,_vm的一般在initState中初始化
class HomePageViewModel extends Model {
//此处省略2万行
}
@override
void initState() {
super.initState();
_vm = HomePageViewModel();
_vm.reloadData();
//WidgetsBinding.instance.addObserver(this);
}
使用方式很简单, 接下来就到了本博的重点源码分析
源码分析
框架只有一个scoped_model.dart文件, 我们先来分析下Model类
abstract class Model extends Listenable {
Model是一个抽象类,需要我们派生实现它
Listenable是Flutter框架自带的抽象接口,主要用来提供添加和移除观察者对象的接口, 定义如下
abstract class Listenable {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Listenable();
/// Return a [Listenable] that triggers when any of the given [Listenable]s
/// themselves trigger.
///
/// The list must not be changed after this method has been called. Doing so
/// will lead to memory leaks or exceptions.
///
/// The list may contain nulls; they are ignored.
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;
/// Register a closure to be called when the object notifies its listeners.
void addListener(VoidCallback listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies.
void removeListener(VoidCallback listener);
}
Model负责实现该接口,具体实现很简单,使用Set来管理观察者
abstract class Model extends Listenable {
final Set<VoidCallback> _listeners = Set<VoidCallback>();
/// [listener] will be invoked when the model changes.
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
/// [listener] will no longer be invoked when the model changes.
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
数据模型变化时如何通知订阅者呢,notifyListeners方法就是用来通知订阅者,我们数据发生变化,如果需要通知订阅者重新渲染,必须调用该方法,如果遇到数据变化,订阅者(Widget)没有重新渲染界面,一般都是没有调用notifyListeners惹得锅。
@protected
void notifyListeners() {
// We schedule a microtask to debounce multiple changes that can occur
// all at once.
if (_microtaskVersion == _version) {
_microtaskVersion++;
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
// Convert the Set to a List before executing each listener. This
// prevents errors that can arise if a listener removes itself during
// invocation!
_listeners.toList().forEach((VoidCallback listener) => listener());
});
}
}
主要通知所有的订阅者数据的更新,没什么好说的,可能有点疑惑的就是_microtaskVersion, _version这两变量的作用,它们是用于防止多次调用notifyListeners方法导致的多次渲染页面浪费cpu,刷新页面是异步的,notifyListeners方法调用完不会立即刷新页面,下一次进入looper时才会通知订阅者
分析完了Model,我们来看看怎么使用它
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
// First, increment the counter
_counter++;
// Then notify all the listeners.
notifyListeners();
}
}
接下来我们分析下ScopedModel
class ScopedModel<T extends Model> extends StatelessWidget {
/// The [Model] to provide to [child] and its descendants.
final T model;
/// The [Widget] the [model] will be available to.
final Widget child;
ScopedModel({@required this.model, @required this.child})
: assert(model != null),
assert(child != null);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
从定义我们看出ScopedModel是一个无状态的Widget
build方法只返回来AnimatedBuilder,AnimatedBuilder 专门用来处理动画,将动画 和要作用的 Widget 关联起来,我们看看它的构造函数
const AnimatedBuilder({
Key key,
@required Listenable animation,
@required this.builder,
this.child,
})
参数animation是一个Listenable类型,当它发生变化时会调用builder重新渲染界面
build方法传的参数是Model,Model也是一个Listenable类型,这里我们就知道了为什么模型变化时,订阅者为何能刷新界面了,builder方法即为重新渲染的内容
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
class _InheritedModel<T extends Model> extends InheritedWidget {
final T model;
final int version;
_InheritedModel({Key key, Widget child, T model})
: this.model = model,
this.version = model._version,
super(key: key, child: child);
@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
(oldWidget.version != version);
}
_InheritedModel派生自InheritedWidget,InheritedWidget是一个特殊的widget,可以存储和获取数据,子孙组件可以通过Element链获取到存储的数据,数据变化是否通知订阅者重新渲染页面取决于updateShouldNotify方法,version用与和上一次更新的version比较,为了避免一个方法中多次调用updateShouldNotify导致重复刷新页面浪费CPU, version如果不同就通知订阅者(子孙Widget)重新渲染页面.
子widget到底是在哪订阅的呢。其实订阅的部分都隐没在框架里,不需要我们手动订阅。让我们来瞧一瞧其真面目。
在使用ScopedModel的使用用到ScopedModelDescendant,先看下其源码
class ScopedModelDescendant<T extends Model> extends StatelessWidget {
/// Called whenever the [Model] changes.
final ScopedModelDescendantBuilder<T> builder;
/// An optional constant child that does not depend on the model. This will
/// be passed as the child of [builder].
final Widget child;
/// An optional constant that determines whether the
final bool rebuildOnChange;
/// Constructor.
ScopedModelDescendant({
@required this.builder,
this.child,
this.rebuildOnChange = true,
});
@override
Widget build(BuildContext context) {
return builder(
context,
child,
ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
);
}
}
源码很少,rebuildOnChange 默认为true,表示model变化时会通知builder方法重建widget,否则model变化时不会调用builder重建, Widget build(BuildContext context)方法中,调用了 ScopedModel.of(context, rebuildOnChange: rebuildOnChange)获取Model数据,并将它传给builder方法,我们着重看下该方法
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();
Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorWidgetOfExactType(type);
if (widget == null) {
throw new ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}
方法主要是通过订阅widget 访问祖先widget查找到_InheritedModel,然后获取其model,如果找不到就报错,rebuildOnChange的值不同调用查找的方法不同,我们先看下rebuildOnChange为true时,这时调用context.inheritFromWidgetOfExactType(type),它就是用来订阅model变化时方法,
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
订阅的地方是
return inheritFromElement(ancestor, aspect: aspect);
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
return dependOnInheritedElement(ancestor, aspect: aspect);
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
主要订阅代码
_dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);
这些都是flutter框架代码,有兴趣的大家自己研究下,rebuildOnChange为false时,调用的是
context.ancestorWidgetOfExactType(type);
它的作用仅仅访问但不订阅InheritedWidget的变化,具体源码如下
@override
Widget ancestorWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
Widget都有Element与之对应,Widget只是配置Element的数据,Widget每次重建Element却不一定重建,它只是根据配置(Widget)来进行Element刷新重建和销毁,子Element节点可以访问父Element,通过这种方式,不管Widget层级多深都能访问到祖先节点的数据