Flutter框架分析(七)-relayoutBoundary

1. 前言

Flutter框架分析(四)-RenderObject一文中,咱们简单介绍了RenderObject中一个重要成员变量:RelayoutBoundary。下面咱们简单回顾下RelayoutBoundary的主要做用。
当一个组件的大小被改变时,其parent的大小可能也会被影响,所以须要通知其父节点。若是这样迭代上去,须要通知整棵RenderObject Tree从新布局,必然会影响布局效率。所以,Flutter经过RelayoutBoundaryRenderObject Tree分段,若是遇到了RelayoutBoundary,则不去通知其父节点从新布局,由于其大小不会影响父节点的大小。这样就只须要对RenderObject Tree中的一段从新布局,提升了布局效率。
那么,RelayoutBoundary是怎么实现将RenderObject Tree分段的呢?本文将经过源码来剖析RelayoutBoundary的工做原理。node

2. 源码解析

Flutter中,若是Widget有更新,须要从新布局,Framework会将须要布局的RenderObject加入PipelineOwner的_nodesNeedingLayout中,而后当下一个VSync信号来临时,Framework会遍历_nodesNeedingLayout,对其中的每个RenderObject从新进行布局,遍历_nodesNeedingLayout的函数源码以下:markdown

void flushLayout() {
  try {
    // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
    while (_nodesNeedingLayout.isNotEmpty) {
      final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
      _nodesNeedingLayout = <RenderObject>[];
      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
        if (node._needsLayout && node.owner == this)
          node._layoutWithoutResize();
      }
    }
  } finally {
  }
}
复制代码

其中,_layoutWithoutResize会调用RenderObjectperformLayout函数,实现该RenderObject的从新布局。
以上流程的示意图以下:架构

image.gif

由上述逻辑可知,当Widget有更新,须要从新布局时,加入_nodesNeedingLayout的元素的多少直接关系到须要从新布局元素的多少,若是能将尽量少的RenderObject加入_layoutWithoutResize,便可尽量提升布局效率。这就是设计RelayoutBoundary的核心思路。
下面咱们来看何时会将RenderObject添加进_nodesNeedingLayout。从源码能够看到,添加进_nodesNeedingLayout有两个地方:框架

  • 初始化RenderView的时候,源码以下:
void scheduleInitialLayout() {
  _relayoutBoundary = this;
  owner._nodesNeedingLayout.add(this);
}
复制代码

本函数只在Flutter初始化的时候调用一次。函数

  • RenderObject标记本身须要从新布局的时候,源码以下:
void markNeedsLayout() {
  if (_needsLayout) {
    return;
  }
  if (_relayoutBoundary != this) {
    markParentNeedsLayout();
  } else {
    _needsLayout = true;
    if (owner != null) {
      owner._nodesNeedingLayout.add(this);
      owner.requestVisualUpdate();
    }
  }
}
复制代码

那本函数的调用时机是什么呢?主要有如下几种:oop

  • 子节点变更,例如attachdetach
  • 自身布局变化,例如Size变化。

Flutter初始化进行第一次布局,每一个RenderObject均须要布局,所以无优化空间,本文主要关注对从新布局的优化,即对markNeedsLayout的调用。接下来咱们分析markNeedsLayout的调用链。其流程图以下:源码分析

image.gif

可见,在一个RenderObject调用markNeedsLayout函数后,若是其自己不是_relayoutBoundary,则会经过markParentNeedsLayout函数调用到parentmarkNeedsLayout函数,从而造成递归调用,直到找到最近的一个是_relayoutBoundary的上级节点,才会中止递归,并将该节点加入_nodesNeedingLayout。所以,经过_relayoutBoundary,FlutterRenderObject Tree划分红了数段,当位于某段的RenderObject须要从新布局时,只会更新该段及其下的RenderObject,而不是整个RenderObject Tree。示意图以下:布局

image.gif

那么,何时会将RenderObject设置为RelayoutBoundary呢?知足如下4种状况之一时,会将自身设置为RelayoutBoundarypost

  • parentUsesSize = false:父节点的布局不依赖当前节点的大小。
  • sizedByParent = true:当前节点大小由父节点决定。
  • constraints.isTight:大小为肯定的值,即宽高的最大值等于最小值。
  • parent is not RenderObject:若是父节点不是RenderObject,子节点layout变化不须要通知父节点更新。

以上条件很好理解,例如parentUsesSize = false,此时父节点的布局不依赖当前节点的大小,那当前节点布局更新天然不须要通知父节点,所以能够将其做为一个RelayoutBoundary性能

3. 小结

本文首先介绍了RelayoutBoundary的做用,而后结合源码分析了RelayoutBoundary的做用原理,其重点以下:

  1. RelayoutBoundary经过减小待布局节点列表数量(加入_nodesNeedingLayout)的方式优化节点更新时的布局效率。
  2. RelayoutBoundary的设置条件包括如下4种:
  • parentUsesSize = false
  • sizedByParent = true
  • constraints.isTight
  • parent is not RenderObjec

4. 参考文档

如何在Flutter上实现高性能的动态模板渲染

5. 相关文章

Flutter框架分析(一)--架构总览
Flutter框架分析(二)-- Widget
Flutter框架分析(三)-- Element
Flutter框架分析(四)-RenderObject
Flutter框架分析(五)-Widget,Element,RenderObject树
Flutter框架分析(六)-Constraint
Flutter框架分析(八)-Platform Channel
Flutter框架分析- Parent Data
Flutter框架分析 -InheritedWidget

相关文章
相关标签/搜索