likes
comments
collection
share

Provider简介

作者站长头像
站长
· 阅读数 0

核心类图

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

Provider简介

ps: 只有一个子结点的树, 看起来确实跟链表有点像

首先, 在main中函数中, 我们会调用runApp方法, 在runApp方法中, RunApp中会调用attachRootWidget方法,该方法会自动生成三棵树的根节点

RenderObjectToWidgetAdapterWidget树的根节点, child是调用runApp函数, 传入的参数

RenderObjectToWidgetElementElement树的根节点

RenderObjectWithChildMixinRenderObject树的根节点

 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树

RenderObjectToWidgetElementmount的时候, 会构建出一棵完整的Element

通过拿到RenderObjectToWidgetAdapterchild, 来拿到其child对应的Element节点,让该节点执行mount操作

如果ElementComponentElement类型

  1. mount的过程中, 会调用Elementbuild方法, 在这个build方法中, 又会调用WidgetStatebuild方法
  2. mount方法中, 会拿到build方法的Widget返回值, 通过Widget返回值拿到对应的Element, 然后再次递归执行mount操作

RenderObject 树

RenderObject树是一个Element的裁剪版, 如果ElementRenderObjectElement 类型的, 那么就会在mount过程中, 生成RenderObject, 并附着到RenderObject树中

这样设计的原因是:

Widget只是一个配置对象, 新建的开销很小

RenderObject负责渲染/布局等, 生成开销比较大, 所以设计了Element树, Element有更新机制, 如果类型一致的情况下, 不会重新生成Element, 而是用widget更新Element, 这样就能减少在Widget重新build时生成RenderObject的次数

Nested

Provider简介

其中这几个都是Nestd这个库的一部分, 这个库的主要作用是防止嵌套层级过深

伪代码比较


// Nested:
MultiProvider(
  providers: [
    ChangeNotifierProviderA(),
    ChangeNotifierProviderB(),
  ],
  child: const MyApp(),
)


// flutter原生:
MulitProvider(
    child: ChangeNotifierProviderA(
        child: ChangeNotifierProviderB(
            child: MyApp()
        )
    )
)

Element树的对比

Nested:

Provider简介

flutter原生:

Provider简介

只关注中间的一条线, 可以看到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 _NestedElementbuild方法中,我们能看到Nested中在Widget树中悄然混入了一个全新的Widget类型_NestedHook, 其对应的Element_NestedHookElement, Nested根据其children倒序来新建对应的_NestedHook

在第i个_NestedHook中保存了三个属性

owner固定是this

wrappedWidgetchildren第i个元素

injectedChild

 if i < children.count - 1 
     return  第 i+1 个 _NestedHook
 else 
     return  传入的参数 child

到这里, 我们就明白了图上injectedChildwrappedWidget的指向的原因了

Provider简介

_parent指向

看一下_NestedElement的类声明, 可以看到 withSingleChildWidgetElementMixin

class _NestedElement extends StatelessElement with SingleChildWidgetElementMixin 

而SingleChildWidgetElementMixin的实现是这样的,重写了mountactive的时候, 在过程中会判断父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的指向原因也就可以明白了

Provider简介

Element树的构造过程

从上文提到的Element树构造的过程, 可以得出结论, 在遇到ComponentElement时,build过程是产生子树最重要的一环

首先看_NestedHookElementbuild方法

class _NestedHookElement {
 @override
 Widget build() {
    return wrappedChild!;
  }
}

Provider简介

因为出了MulitProvider外, 所有的Provider都会继承自 SingleChildStatelessWidget, 所以, 我们查一下SingleChildStatelessElementbuild方法

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会调用widgetbuildWithChild, 并且会将_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;
}

到这里, 为什么 ChangeProviderElement树会对应成_InheritedProviderScopeElement, 以及整个树的构造过程就清楚了

Provider简介

核心原理

主要是利用InheritedWidget

InheritedWidget读取值

因为Elementmount的时候, 会调用_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是对应widgetruntimeType,value是层级上距离这个Element最近的, widget类型为runtimeTypeInheritedElement

所以, 可以根据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 中有一个_dependentsmap, key是一个Element, 在update InheritedElement的时候, 会根据map中的key, 来通知所有依赖该InheritedWidgetElement重新执行build操作


class InheritedElement {
      @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget)) {
          for (final Element dependent in _dependents.keys) {
          // 强制标脏, 下一帧会重新build
              dependent.markNeedsBuild()
          }
     }
   }
}

rebuild调用在BuildOwnerbuildScope

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;
      }
}

可以看到, readwatch唯一的区别就是listen是false还是true, 影响是否执行dependOnInheritedWidgetOfExactType, 也就说如果使用watch, 相比较于read, 如果InheritedElementupdate时, 会触发自身的rebuild

Consumer 和 Selector

Consumer本质也使用了Provider.of,跟watch类似

class Consumer {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
  return builder(
    context,
    Provider.of<T>(context),
    child,
  );
}
}

SelectorConsumer 类似, 相较于Consumer, 多个一份缓存, 可以提高一些性能

  1. 利用widget.selector(context);拿到变化后的数据

  2. 调用_shouldRebuild, 参数是变化后的数据, 跟原来的数据, 返回true, 则rebuild, 返回false, 就继续第三步

  3. 变化后的数据, 跟原来的数据, 利用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, 来通知所有依赖该InheritedWidgetElement重新执行build操作

在第一次获取value的时候, 会触发ChangeNotifierProvidercreate属性, 以及 startListening属性, 此后, 如果value如果调用notifyListeners, 就会触发监听, 让依赖项全部rebuild

class ListenableProvider {
  static VoidCallback _startListening(
    InheritedContext e,
    Listenable? value,
  ) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
 }

相关资料:

gityuan.com/2019/06/29/…

gityuan.com/2019/06/15/…

www.liujunmin.com/flutter/pro…