Provider简介
核心类图
简介flutter三棵树
void main() {
runApp(
const MyApp()
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HelloWorldPage(),
);
}
}
class HelloWorldPage extends StatelessWidget {
const HelloWorldPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text("123"),
);
}
}
ps: 只有一个子结点的树, 看起来确实跟链表有点像
首先, 在main
中函数中, 我们会调用runApp
方法, 在runApp
方法中, RunApp
中会调用attachRootWidget
方法,该方法会自动生成三棵树的根节点
RenderObjectToWidgetAdapter
是Widget
树的根节点, child
是调用runApp
函数, 传入的参数
RenderObjectToWidgetElement
是Element
树的根节点
RenderObjectWithChildMixin
是RenderObject
树的根节点
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
// 1. 新建RenderObjectToWidgetAdapter, 并调用attachToRenderTree
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance!.ensureVisualUpdate();
}
}
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
// 2. 新建RenderObjectToWidgetAdapter对应的Element, 也是Element树的根节点
element = createElement();
});
owner.buildScope(element!, () {
// 3. 构建Element树
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
Widget 树
widget是一颗逻辑树, 与我们书写build
方法中的Widget
结构一致
Element树
RenderObjectToWidgetElement
在mount
的时候, 会构建出一棵完整的Element
树
通过拿到RenderObjectToWidgetAdapter
的child
, 来拿到其child
对应的Element
节点,让该节点执行mount
操作
如果Element
是ComponentElement
类型
- 在
mount
的过程中, 会调用Element
的build
方法, 在这个build
方法中, 又会调用Widget
或State
的build
方法 mount
方法中, 会拿到build
方法的Widget
返回值, 通过Widget
返回值拿到对应的Element
, 然后再次递归执行mount
操作
RenderObject 树
RenderObject
树是一个Element
的裁剪版, 如果Element
是RenderObjectElement
类型的, 那么就会在mount
过程中, 生成RenderObject
, 并附着到RenderObject
树中
这样设计的原因是:
Widget
只是一个配置对象, 新建的开销很小
RenderObject
负责渲染/布局等, 生成开销比较大, 所以设计了Element
树, Element
有更新机制, 如果类型一致的情况下, 不会重新生成Element
, 而是用widget
更新Element
, 这样就能减少在Widget
重新build
时生成RenderObject
的次数
Nested
其中这几个都是Nestd这个库的一部分, 这个库的主要作用是防止嵌套层级过深
伪代码比较
// Nested:
MultiProvider(
providers: [
ChangeNotifierProviderA(),
ChangeNotifierProviderB(),
],
child: const MyApp(),
)
// flutter原生:
MulitProvider(
child: ChangeNotifierProviderA(
child: ChangeNotifierProviderB(
child: MyApp()
)
)
)
Element树的对比
Nested:
flutter原生:
只关注中间的一条线, 可以看到Element树基本是一致, 只是多了两个__NestedHookElement
, 但是这两个__NestedHookElement
是继承至StatelessElement
的, 不会有RenderObject
, 所以RenderObject
树是一致的, 展示并不会有影响
Nested Element树的原因
injectedChild 和 wrappedWidget的指向
ps: 为了降低难度, 聚焦核心, 列出的代码全部是经过精简过的代码
Nested({
Key? key,
required List<SingleChildWidget> children,
Widget? child,
})
class _NestedElement {
Widget build() {
var nextNode = widget._child;
for (final child in widget._children.reversed) {
nextNode = nestedHook = _NestedHook(
owner: this,
wrappedWidget: child,
injectedChild: nextNode,
);
}
return nextNode;
}
}
根据widget
Nested
对应的Element
_NestedElement
的build
方法中,我们能看到Nested
中在Widget
树中悄然混入了一个全新的Widget
类型_NestedHook
, 其对应的Element
为_NestedHookElement
, Nested
根据其children
倒序来新建对应的_NestedHook
在第i个_NestedHook
中保存了三个属性
owner
固定是this
wrappedWidget
是children
第i个元素
injectedChild
是
if i < children.count - 1
return 第 i+1 个 _NestedHook
else
return 传入的参数 child
到这里, 我们就明白了图上injectedChild
和wrappedWidget
的指向的原因了
_parent指向
看一下_NestedElement的类声明, 可以看到 with
了SingleChildWidgetElementMixin
class _NestedElement extends StatelessElement with SingleChildWidgetElementMixin
而SingleChildWidgetElementMixin的实现是这样的,重写了mount
和active
的时候, 在过程中会判断父ELement
是否是_NestedHookElement
类型, 如果是, 就会将 _parent
指向父ELement
mixin SingleChildWidgetElementMixin on Element {
_NestedHookElement? _parent;
@override
void mount(Element? parent, dynamic newSlot) {
if (parent is _NestedHookElement?) {
_parent = parent;
}
super.mount(parent, newSlot);
}
@override
void activate() {
super.activate();
visitAncestorElements((parent) {
if (parent is _NestedHookElement) {
_parent = parent;
}
return false;
});
}
}
所以,_parent
的指向原因也就可以明白了
Element树的构造过程
从上文提到的Element
树构造的过程, 可以得出结论, 在遇到ComponentElement
时,build
过程是产生子树最重要的一环
首先看_NestedHookElement
的build
方法
class _NestedHookElement {
@override
Widget build() {
return wrappedChild!;
}
}
因为出了MulitProvider
外, 所有的Provider
都会继承自 SingleChildStatelessWidget
, 所以, 我们查一下SingleChildStatelessElement
的build
方法
class SingleChildStatelessElement {
@override
Widget build() {
if (_parent != null) {
return widget.buildWithChild(this, _parent!.injectedChild);
}
return super.build();
}
}
abstract class SingleChildStatelessWidget {
Widget buildWithChild(BuildContext context, Widget? child);
}
从这里可以看出SingleChildStatelessElement
会调用widget
的buildWithChild
, 并且会将_parent!.injectedChild
作为参数传入, 我们顺着changeProvider
的继承链找, 就会发现如下代码:
class InheritedProvider {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return _InheritedProviderScope<T?>(
owner: this,
child: builder != null
? Builder(
builder: (context) => builder!(context, child),
)
: child!
);
}
}
而且ChangeProvider
自身没有实现createElement
方法, 然后我们顺着继承链, 发现到_InheritedProviderScope
,才有对应的createElement
, 对应的Element
是_InheritedProviderScopeElement
, 再根据其继承链 -> InheritedElement -> ProxyElement
,就会发现如下代码
abstract class ProxyElement extends ComponentElement {
@override
Widget build() => widget.child;
}
到这里, 为什么 ChangeProvider
在Element
树会对应成_InheritedProviderScopeElement
, 以及整个树的构造过程就清楚了
核心原理
主要是利用InheritedWidget
从InheritedWidget
读取值
因为Element
在mount
的时候, 会调用_updateInheritance
, 其中_updateInheritance
class Element {
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
}
但是在InheritedElement
的时候, 会重写_updateInheritance
方法
class InheritedElement {
@override
void _updateInheritance() {
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
都会有一个_inheritedWidgets
字典, key
是对应widget
的runtimeType
,value
是层级上距离这个Element
最近的, widget
类型为runtimeType
的InheritedElement
所以, 可以根据Element
的_inheritedWidgets
字典,以及InheritedWidget
类型,就能找到距离自己最近的InheritedElement
, 如果数据可以存在InheritedElement
这里, 那么InheritedElement
下所有的Element
树节点都可以通过拿到InheritedElement
来拿到数据
flutter已经帮我们实现好了这个方法, getElementForInheritedWidgetOfExactType
class Element {
@override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
return ancestor;
}
}
同步InheritedWidget
的数据变化
InheritedWidget
中有一个_dependents
map, key是一个Element
, 在update
InheritedElement
的时候, 会根据map
中的key
, 来通知所有依赖该InheritedWidget
的Element
重新执行build
操作
class InheritedElement {
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget)) {
for (final Element dependent in _dependents.keys) {
// 强制标脏, 下一帧会重新build
dependent.markNeedsBuild()
}
}
}
}
rebuild调用在BuildOwner
的buildScope
中
class BuildOwner {
void buildScope(Element context, [VoidCallback? callback]) {
// 一堆代码
_dirtyElements[index].rebuild();
// 一堆代码
}
}
那么,只要某Element
能令InheritedElement
的_dependents
字典包含自己, 那么InheritedElement
变化的时候就能通知到自己.
而dependOnInheritedWidgetOfExactType
就可以做到这一点
class Element {
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
// 核心这里
ancestor._dependents[this] = null;
return ancestor.widget;
}
_hadUnsatisfiedDependencies = true;
return null;
}
}
核心代码
read / watch 的区别及原理
ps: 为了降低复杂度, 突出核心, 代码经过整理和删减
extension ReadContext on BuildContext {
T read<T>() {
return Provider.of<T>(this, listen: false);
}
}
extension ReadContext on BuildContext {
T watch<T>() {
return Provider.of<T>(this);
}
}
class Provider<T> extends InheritedProvider<T> {
static T of<T>(BuildContext context, {bool listen = true}) {
// 找到Element树中, 离你最近的Widget类型为_InheritedProviderScope<T>的Element
// 核心:
final inheritedElement = context.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T?>>() as _InheritedProviderScopeElement<T?>?;
if (listen) {
// 核心: 给添加依赖
context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
}
final value = inheritedElement?.value;
return value as T;
}
}
可以看到, read
和watch
唯一的区别就是listen是false
还是true
, 影响是否执行dependOnInheritedWidgetOfExactType
, 也就说如果使用watch
, 相比较于read
, 如果InheritedElement
在update
时, 会触发自身的rebuild
Consumer 和 Selector
Consumer
本质也使用了Provider.of
,跟watch
类似
class Consumer {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
Selector
与 Consumer
类似, 相较于Consumer
, 多个一份缓存, 可以提高一些性能
-
利用
widget.selector(context);
拿到变化后的数据 -
调用
_shouldRebuild
, 参数是变化后的数据, 跟原来的数据, 返回true, 则rebuild
, 返回false, 就继续第三步 -
变化后的数据, 跟原来的数据, 利用
DeepCollectionEquality().equals
比较, 相等的话, 就走缓存, 否则rebuild
class _Selector0State {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
final selected = widget.selector(context);
final shouldInvalidateCache = oldWidget != widget ||
(widget._shouldRebuild != null &&
widget._shouldRebuild!(value as T, selected)) ||
(widget._shouldRebuild == null &&
!const DeepCollectionEquality().equals(value, selected));
if (shouldInvalidateCache) {
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
return cache!;
}
}
}
ChangeProvider
ChangeNotifierProvider
继承至 ListenableProvider
, 又继承至InheritedProvider
ListenableProvider
将自己的_startListening
方法, 作为自己的startListening
属性, 在这个方法中, 会给自己的value
增加监听, 监听的回调时notifyClients
,notifyClients
中会根据map
中的key
, 来通知所有依赖该InheritedWidget
的Element
重新执行build
操作
在第一次获取value
的时候, 会触发ChangeNotifierProvider
的create
属性, 以及 startListening
属性, 此后, 如果value
如果调用notifyListeners
, 就会触发监听, 让依赖项全部rebuild
class ListenableProvider {
static VoidCallback _startListening(
InheritedContext e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
相关资料:
转载自:https://juejin.cn/post/7021121891402448904