【Flutter核心类分析】深入理解BuildOwner

背景

在之前的文章【Flutter原理】三棵树的诞生与核心流程一文中,我们第一次接触到了buildOwner,也就是在WidgetsBinding对象的attachRootWidget(widget rootWidget)方法中,我们现在来回顾一下:

  void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);

文章只是简单介绍了下BuildOwner可以理解为widgets管理器,至于它的具体来源与作用,就是我们本文分析的主要内容。


BuildOwner来源

接着上面的代码继续来看buildOwner是如何初始化的

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  // .......
  @override
  void initInstances() {
    super.initInstances();
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
  }
  // ......
}

我们看到buildOwner的初始化是在mixin类WidgetsBinding的initInstances方法中初始化,它的调用者也就是在我们之前分析的FLutter APP启动的runApp()方法。

既然_buildOwner是在WidgetsBinding中初始化的,那么_buildOwner又是交给谁去使用呢?谁来管理,用来干啥?我们继续看最上面调用的attachToRenderTree方法

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      //element 为空,说明是rootElement
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        //步骤1 给rootElement分配Owner
        element!.assignOwner(owner);
      });
      //步骤2 调用buildScope
      owner.buildScope(element!, () {
        //步骤3
        element!.mount(null, null);
      });
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }
  • 步骤1:我们看到如果element == null的时候,就会就会创建rootElement调用rootEelement.assignOwner(owner),将owner交给element,
  • 步骤2:调用buildScope方法,我们先继续看owner.buildScope方法
// 去掉一大堆的assert方法,保留核心逻辑
void buildScope(Element context, [ VoidCallback callback ]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;

  try {
    if (callback != null) {
      //先执行回调
      callback();
    }

    _dirtyElements.sort(Element._sort);
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      _dirtyElements[index].rebuild();
      index += 1;
    }
  } finally {
    for (Element element in _dirtyElements) {
      element._inDirtyList = false;
    }
    _dirtyElements.clear();
  }
}
  • 有回调,就先执行回调,也就是执行上面的element!.mount(null, null);方法。

  • 对_dirtyElements进行排序,根据Element的depth进行排序,按照element tree上的深度

    这样做的好处就是确保parent优先于child被rebuild,避免child被重复的rebuild(因为parent在rebuild的时候会递归的updateChild)

  • 对_dirtyElement中的元素异常调用rebuild(_dirtyElements[index].rebuild()

  • 清理_dirtyElement,将所有的 _dirtyElement设置为false。

我们再来看element!.mount方法:

  @override
  void mount(Element? parent, dynamic newSlot) {
    assert(parent == null);
    // 步骤1
    super.mount(parent, newSlot);
    // 步骤2
    _rebuild();
  }

这里调用super.mount()也就是最终调用到了基类element.mount方法,我们稍后继续看

 void mount(Element? parent, dynamic newSlot) {
    // .... 一大堆的assert
   
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) // Only assign ownership if the parent is non-null
      // 这里直接将parent.owner赋值给了child
      _owner = parent.owner;
    final Key? key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }

好了到了这里我们来个阶段性的总结

BuildOwner实例由WidgetsBinding负责创建,并赋值给「Element Tree」的根节点RenderObjectToWidgetElement,此后随着「Element Tree」的创建逐级传递给子节点。整棵「Element Tree」共享同一个BuildOwner实例。
一般情况下并不需要我们手动实例化BuildOwner,除非需要离屏沉浸 (此时需要构建 off-screen element tree)

BuildOwner主要作用

要分析BuildOwner的作用,我们首先来看它的两个成员变量

//用于存储收集到的Inactive Elements
final _InactiveElements _inactiveElements = _InactiveElements();
//用于存储手机DirtyElement
final List<Element> _dirtyElements = <Element>[];

Dirty Elements

字面理解Dirty Elements就是脏的Element。那么就好理解了,也就是需要更新的Elements。

问题来了,BuildOwner是如何收集Dirty Elements的呢?我们需要跟到State.setState方法:

void setState(VoidCallback fn) {
   final dynamic result = fn() as dynamic;
   _element!.markNeedsBuild();
}

接下来看markNeedsBuild()

void markNeedsBuild() {
    _dirty = true;
    owner!.scheduleBuildFor(this);
}

markNeedsBuild的主要作用就是设置_dirty属性为true,然后执行BuildOwner.scheduleBuildFor方法

void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
}

scheduleBuildFor的逻辑也很清晰,只做了两件事:

  • 调用onBuildScheduled,该方法实际上是一个回调(也就是文章开始提到的buildOwner!.onBuildScheduled = _handleBuildScheduled;这里这个方法),通知Engine在下一帧时做更新操作
  • 将设置 _dirty为true的element,加入到_dirtyElements中。

在下一帧到来的时候,WidgetsBinding.drawFrame会被调用

void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
  }

WidgetsBinding.drawFrame方法内部,又调用到了buildOwner.buildScope方法。buildScope作用就是rebuild所有的dirty elements再重新清理dirty elements。到了这里就形成了一个完整的闭环。

下面继续看InactiveElement

InactiveElement

所谓InactiveElement,按字面理解就是不活跃的Element,是指从Element Tree上被移除的element,我们来看下Element.deactivateChild方法

  void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner!._inactiveElements.add(child); // this eventually calls child.deactivate()
  }

deactivateChild的作用很明显,也就是将给定的Element移动到非活动的Element列表,在Element在detach的时候会被收集到_inactiveElements。

inactive elements在手动调用finalizeTree的时候会被统一清理。

总结

BuildOwner的主要作用就是

  • 在UI更新过程中跟踪,管理需要rebuild的Element(dirty elements)
  • 在有dirty elements时,及时通知引擎,以便在下一帧安排dirty elements的rebuild,从而去更新UI
  • 管理处于inactive状态的element
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>