【Flutter】熊孩子拆组件系列之拆ListView(九)—— AutomaticKeepAlive和KeepAlive

639 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。

前言

Item这块,前面看了一下貌似没什么太大作用的 KeyedSubtree ,接下来看下这两个貌似跟KeepAlive有关系的东西,他俩又有什么区别;

注释部分

image.png

总结一下,AutoMaticKeepAlive是通过监听KeepAliveNotification,来构建child,处理KeepAlive的一个widget;

image.png

而 KeepAlive 是一个通过给定的数值,来处理KeepAlive的Widget,他并不会自动处理事件;一般用于SliverList和SliverGrid;

运行原理

KeepAlive

首先从简单的KeepAlive开始:

image.png

其所做的事也不复杂:

当被调用ApplyParentData的时候,检查KeepAlive设置,如果不需要KeepAlive,那么触发重绘。否则就不用处理;

当然会更新ParentData中的KeepAlive属性,而那个是在SlverList中,用来判断是否需要放到KeepAliveBucket持久化的判断依据,比如说之前提到的 _destroyOrCacheChild 方法:

image.png

再次拿的时候就会从这个bucket中拿:

image.png

而KeepAlive被触发调用的地方就在AutomaticKeepAlive这块;

AutomaticKeepAlive

首先是AutomaticKeepAlive的build方法:

image.png

只是简单的构造了一个K呃呃篇Alive,并将_child 作为其child;

而_child 是这么构造的:

image.png

结合注释所述,那么核心逻辑就是这个_addClient方法了;

image.png

在这段代码中,所做的事就三件:

  • 通过_createCallback(handler)方法,来创建KeppAliveNotification 的回调;
  • 更新缓存的handle及其回调;
  • 判断更新parentData的时机;

创建callback

这段代码中,注释反而是比代码长,去掉注释部分,代码是这样的:

VoidCallback _createCallback(Listenable handle) {
  return () {
    assert(() {
      if (!mounted) {
        throw FlutterError(
          'AutomaticKeepAlive handle triggered after AutomaticKeepAlive was disposed.\n'
          'Widgets should always trigger their KeepAliveNotification handle when they are '
          'deactivated, so that they (or their handle) do not send spurious events later '
          'when they are no longer in the tree.',
        );
      }
      return true;
    }());
    _handles!.remove(handle);
    if (_handles!.isEmpty) {
      if (SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) {
        setState(() { _keepingAlive = false; });
      } else {
        _keepingAlive = false;
        scheduleMicrotask(() {
          if (mounted && _handles!.isEmpty) {
            setState(() {
              assert(!_keepingAlive);
            });
          }
        });
      }
    }
  };
}

抛开前面的assert部分,其所做的事其实简单的来说就是:

  • 移除缓存的回调;
  • 如果没有缓存的回调了:
    • 如果当前构建过程处于build、layout、paint等方法之前,那么打脏,下一帧再更新;
    • 如果已经开始构建了,那么将打脏操作加入到一个微队列中(也就是说放到下下帧中?);

关于为什么这么做;注释中是这么解释的:

// We were probably notified by a descendant when they were yanked out
// of our subtree somehow. We're probably in the middle of build or
// layout, so there's really nothing we can do to clean up this mess
// short of just scheduling another build to do the cleanup. This is
// very unfortunate, and means (for instance) that garbage collection
// of these resources won't happen for another 16ms.
//
// The problem is there's really no way for us to distinguish these
// cases:
//
//  * We haven't built yet (or missed out chance to build), but
//    someone above us notified our descendant and our descendant is
//    disconnecting from us. If we could mark ourselves dirty we would
//    be able to clean everything this frame. (This is a pretty
//    unlikely scenario in practice. Usually things change before
//    build/layout, not during build/layout.)
//
//  * Our child changed, and as our old child went away, it notified
//    us. We can't setState, since we _just_ built. We can't apply the
//    parent data information to our child because we don't _have_ a
//    child at this instant. We really want to be able to change our
//    mind about how we built, so we can give the KeepAlive widget a
//    new value, but it's too late.
//
//  * A deep descendant in another build scope just got yanked, and in
//    the process notified us. We could apply new parent data
//    information, but it may or may not get applied this frame,
//    depending on whether said child is in the same layout scope.
//
//  * A descendant is being moved from one position under us to
//    another position under us. They just notified us of the removal,
//    at some point in the future they will notify us of the addition.
//    We don't want to do anything. (This is why we check that
//    _handles is still empty below.)
//
//  * We're being notified in the paint phase, or even in a post-frame
//    callback. Either way it is far too late for us to make our
//    parent lay out again this frame, so the garbage won't get
//    collected this frame.
//
//  * We are being torn out of the tree ourselves, as is our
//    descendant, and it notified us while it was being deactivated.
//    We don't need to do anything, but we don't know yet because we
//    haven't been deactivated yet. (This is why we check mounted
//    below before calling setState.)
//
// Long story short, we have to schedule a new frame and request a
// frame there, but this is generally a bad practice, and you should
// avoid it if possible.

简单的来说就是,整个构建过程,应该是一个原子性的操作;所以如果在构建过程中被子View或者其他方式通知了重构,那么只能再安排一次构建来做这个操作;

判断更新parentData的时机

在这一步所做的事:

  • 如果child不为空,那么直接调用 _updateParentDataOfChild 方法;
  • 如果为空,那么就按注释中说的那样,放到结尾再更新ParentData;

而这个 _updateParentDataOfChild 方法所做的事,就是字面意思:

image.png

最终会调用到 KeepAlive 自己的applyParentData方法:

image.png

总结:

这两个Widget所做的事,无非就是通过监听KeepAliveNotification,来更新SliverList中存放的ParentData,进而实现SliverList在销毁的时候不调用Item本身的销毁方法,而是放到一个Map中缓存起来;