likes
comments
collection
share

ValueNotifier及ValueListenableBuilder源码解析

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

背景

之前就使用过ValueNotifierValueListenableBuilder做局部刷新,但是没有看过源码实现,最近几天看某个三方源码的时候发现继承了Listenable,就想了解一下它到底是干嘛的,然后就发现了ValueNotifier的元类就是Listenable,所以今天就跟大家分享一下

文章分为两个部分第一部分简单写下ValueNotifier的使用,第二部分就是我们的重头戏源码解析,了解用法的可以直接看第二趴

ValueNotifier及ValueListenableBuilder使用方法

以flutter create的模板工程为例,我们创建一个类型为ValueNotifier的对象泛型设置为int类型,并进行初始化,初始值为0

ValueNotifier<int> count = ValueNotifier(0);

然后我们将数字显示区域的Text替换成ValueListenableBuilder并在builder中return Text()

ValueListenableBuilder(
    valueListenable: count,
    builder: (context,value,child){
      return Text('${count.value}');
}),

最后将_incrementCounter中的setState修改为count.value++;

void _incrementCounter() {
  count.value++;
}

这时候我们再点击就可以看到数字区域有变化了,怎么样使用起来是不是很简单,其实ValueListenableBuilder中还有个可选参数child,child的使用方法就不介绍了,感兴趣的自己研究一下吧

源码实现介绍

先看下ValueNotifier的源码实现再看ValueListenableBuilder

UML类图

ValueNotifier及ValueListenableBuilder源码解析

Listenable

Listenable是一个抽象类,内部的方法也不多,就一个构造函数一个工厂方法加两个方法,比较简单,重点我们看下子类的实现

abstract class Listenable {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// 文摘const构造函数。这个构造函数允许子类提供
  /// const constructors so that they can be used in const expressions.
  /// Const构造函数,以便在Const表达式中使用
  const Listenable();

  /// Return a [Listenable] that triggers when any of the given [Listenable]s
  /// themselves trigger.
  /// 返回一个[Listenable],当任何给定的[Listenable]本身触发时触发。
  ///
  /// 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);
}

子类实现

ValueListenable

该类也是一个抽象类,只定义了一个构造函数和value的get方法

abstract class ValueListenable<T> extends Listenable {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  /// 文摘const构造函数。这个构造函数允许子类提供const构造函数,以便在const表达式中使用它们。
  const ValueListenable();

  /// The current value of the object. When the value changes, the callbacks
  /// registered with [addListener] will be invoked.
  /// 对象的当前值。当值改变时,注册到[addListener]的回调函数将被调用。
  T get value;
}

ChangeNotifier

这个就是一个比较重要的类了,先来看下成员变量

属性介绍

名称作用
int _count实际监听数量
List _listeners储存回调方法
int _notificationCallStackDepth递归锁
int _reentrantlyRemovedListeners缩容使用
bool _debugDisposed标记是否已经释放(在释放后无法进行添加或删除操作,会报错)

成员变量就上面这么多,也不复杂,接下来看看这些属性在方法中是如何使用的

hasListeners

该方法是判断是否有监听器注册,主要在重写addListenerremoveListener时使用,官方不推荐依赖该方法进行判断行为,实现也非常简单

  bool get hasListeners {
    assert(_debugAssertNotDisposed());
    return _count > 0;
  }

addListener

添加监听事件,会将传入的方法加入_listeners中,另外会在该方法内进行数组的扩容操作,主要通过_count与_listeners.length进行比较, 如果相等则进行扩容的操作,最后将callback方法存入数组中

  void addListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    if (_count == _listeners.length) {
      if (_count == 0) {
        _listeners = List<VoidCallback?>.filled(1, null);
      } else {
        // 扩容
        final List<VoidCallback?> newListeners =
            List<VoidCallback?>.filled(_listeners.length * 2, null);
        for (int i = 0; i < _count; i++) {
          newListeners[i] = _listeners[i];
        }
        _listeners = newListeners;
      }
    }
    _listeners[_count++] = listener;
  }

_removeAt(int index)

该方法是一个私有方法,通过index进行删除监听的操作,其中也会进行数组的缩容操作,判断当_count_listeners的一半时进行缩容操作,这里有个比较巧妙的操作,当_count大于_listeners的一半时,不会使用数组的remove而是置为null,从而达到性能优化的目的

  void _removeAt(int index) {
    _count -= 1;
    if (_count * 2 <= _listeners.length) {
      final List<VoidCallback?> newListeners =
          List<VoidCallback?>.filled(_count, null);

      // Listeners before the index are at the same place.
      for (int i = 0; i < index; i++) newListeners[i] = _listeners[i];

      // Listeners after the index move towards the start of the list.
      for (int i = index; i < _count; i++) newListeners[i] = _listeners[i + 1];

      _listeners = newListeners;
    } else {
      // When there are more listeners than half the length of the list, we only
      // shift our listeners, so that we avoid to reallocate memory for the
      // whole list.
      for (int i = index; i < _count; i++) _listeners[i] = _listeners[i + 1];
      _listeners[_count] = null;
    }
  }

removeListener(VoidCallback listener)

该方法十一个对外暴露的remove方法,首先会for循环找到目标listener,然后会进行一个判断,通过_notificationCallStackDepth判断当前是否在进行迭代,如果在迭代中那先不进行缩容处理,直接将对应位置的Listener置为null,并记录null的数量,如果没有在迭代则调用_removeAt方法

  void removeListener(VoidCallback listener) {
    for (int i = 0; i < _count; i++) {
      final VoidCallback? _listener = _listeners[i];
      if (_listener == listener) {
        if (_notificationCallStackDepth > 0) {
          _listeners[i] = null;
          _reentrantlyRemovedListeners++;
        } else {
          _removeAt(i);
        }
        break;
      }
    }
  }

dispose

没什么好说的直接看代码吧

  void dispose() {
    assert(_debugAssertNotDisposed());
    assert(() {
      _debugDisposed = true;
      return true;
    }());
  }

notifyListeners

这个是个重点的方法,通知所有监听器,首先通过_notificationCallStackDepth++进行加锁,然后通过遍历调用_listeners中的方法,当遍历完成时使用_notificationCallStackDepth--进行解锁

然后判断_notificationCallStackDepth是否为0,主要为了判断是否结束了所有的通知,然后判断_reentrantlyRemovedListeners > 0,为了判断数组是否有占位null,当两者同时满足时真正的删除监听器, 这时计算出真正的有用监听器,当newLength为_listeners数量一半时,创建一个数组进行缩容,否则通过排序算法将null全部排在最后面

_reentrantlyRemovedListeners置0,_count赋值为newLength

  void notifyListeners() {

    _notificationCallStackDepth++;

    final int end = _count;
    for (int i = 0; i < end; i++) {
        _listeners[i]?.call();
    }

    _notificationCallStackDepth--;

    if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) {
      final int newLength = _count - _reentrantlyRemovedListeners;
      if (newLength * 2 <= _listeners.length) {
        final List<VoidCallback?> newListeners =
            List<VoidCallback?>.filled(newLength, null);

        int newIndex = 0;
        for (int i = 0; i < _count; i++) {
          final VoidCallback? listener = _listeners[i];
          if (listener != null) {
            newListeners[newIndex++] = listener;
          }
        }

        _listeners = newListeners;
      } else {
        for (int i = 0; i < newLength; i += 1) {
          if (_listeners[i] == null) {
            int swapIndex = i + 1;
            while (_listeners[swapIndex] == null) {
              swapIndex += 1;
            }
            _listeners[i] = _listeners[swapIndex];
            _listeners[swapIndex] = null;
          }
        }
      }

      _reentrantlyRemovedListeners = 0;
      _count = newLength;
    }
  }

_MergingListenable

该类是个私有类,主要用于批量操作Listenable的,我没有找到实际应用,代码也比较简单不介绍了,上代码

class _MergingListenable extends Listenable {
  _MergingListenable(this._children);

  final List<Listenable?> _children;

  @override
  void addListener(VoidCallback listener) {
    for (final Listenable? child in _children) {
      child?.addListener(listener);
    }
  }

  @override
  void removeListener(VoidCallback listener) {
    for (final Listenable? child in _children) {
      child?.removeListener(listener);
    }
  }

  @override
  String toString() {
    return 'Listenable.merge([${_children.join(", ")}])';
  }
}

ValueNotifier

该类是个实现类,也是我们经常使用的类,主要继承于ChangeNotifier实现了ValueListenable,主要代码其实都在ChangeNotifier中,该类主要添加了value的get、set方法

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value);

  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue) return;
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}

ValueListenableBuilder

ValueListenableBuilder主要搭配ValueNotifier使用,该类是个StatefulWidget类,

在initState中注册_valueChanged

在didUpdateWidget判断新老valueListenable是否一致如果不一致则注销旧的方法,注册新的方法,

核心方法在_valueChanged中,当ValueNotifier中的value发生变化时,会调用_valueChanged方法,_valueChanged中会调用setState()进行调用build方法,build方法会会标builder方法并把参数进行回传,没错ValueListenableBuilder本质还是setState,并没有什么黑魔法

class _ValueListenableBuilderState<T> extends State<ValueListenableBuilder<T>> {
  late T value;

  @override
  void initState() {
    super.initState();
    value = widget.valueListenable.value;
    widget.valueListenable.addListener(_valueChanged);
  }

  @override
  void didUpdateWidget(ValueListenableBuilder<T> oldWidget) {
    if (oldWidget.valueListenable != widget.valueListenable) {
      oldWidget.valueListenable.removeListener(_valueChanged);
      value = widget.valueListenable.value;
      widget.valueListenable.addListener(_valueChanged);
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    widget.valueListenable.removeListener(_valueChanged);
    super.dispose();
  }

  void _valueChanged() {
    setState(() {
      value = widget.valueListenable.value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, value, widget.child);
  }
}

小彩蛋

关于源码中的性能优化,本来在看代码的时候,觉得ChangeNotifier实现扩容缩容是性能优化的一部分,然后就想去看看源码中List的扩容缩容的实现是什么样的,没有找到,add方法中并不包含扩容的实现,而是很简单的添加进数组中

  void add(E element) {
    this[this.length++] = element;
  }

反而在看remove方法的时候,觉得可能主要的性能优化在这个地方,在remove对象结束的时候会将后面的对象向前移动,这就导致每次删除时都需要进行移动操作,而在ChangeNotifier的实现中,将要删除的对象置为null,一次性全部删除掉就比较方便

  bool remove(Object? element) {
    for (int i = 0; i < this.length; i++) {
      if (this[i] == element) {
        this._closeGap(i, i + 1);
        return true;
      }
    }
    return false;
  }

  /// Removes elements from the list starting at [start] up to but not including
  /// [end].  Arguments are pre-validated.
  void _closeGap(int start, int end) {
    int length = this.length;
    assert(0 <= start);
    assert(start < end);
    assert(end <= length);
    int size = end - start;
    for (int i = end; i < length; i++) {
      this[i - size] = this[i];
    }
    this.length = length - size;
  }

如果有知道List的扩容和缩容实现在什么地方的麻烦告知一下,感谢

转载自:https://juejin.cn/post/7088687841290485768
评论
请登录