Flutter Widget框架-渲染原理解析

2019-04-10  本文已影响0人  沐沐小火柴

Flutter Framework

视图树的创建与管理机制、布局、渲染核心框架

视图树

渲染机制

调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树

runApp(首次执行)

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

runApp(rootWidget) => attachRootWidget(rootWidget) => attachToRenderTree() => element.mount() => _rebuild() => updateChild()

1. WidgetsFlutterBinding

WidgetsFlutterBinding混入了不少的其他的Binding

持有BuildOwner、PipelineOwner

2. attachRootWidget

3. scheduleWarmUpFrame

安排一个帧尽快运行, 这在应用程序启动期间使用,以便第一个帧(可能非常昂贵)可以多运行几毫秒。(个人理解是为了实现第一次页面渲染可以调用到 => drawFrame)

setState(更新)

@protected
void setState(VoidCallback fn) {
    ...
    _element.markNeedsBuild();
}

1.Element标记自身为dirty,并通知buildOwner处理

void markNeedsBuild() {
    ...
    _dirty = true; // 标记自身为dirty
    owner.scheduleBuildFor(this); // 通知buildOwner处理
  }

2.buildOwner将element添加到集合_dirtyElements中,并通知ui.window安排新的一帧

buildOwner会将所有dirty的Element添加到_dirtyElements当中,等待下一帧绘制时集中处理。

还会调用ui.window.scheduleFrame();通知底层渲染引擎安排新的一帧处理。

void scheduleBuildFor(Element element) {
    ...
    _dirtyElements.add(element);
    element._inDirtyList = true;
    ...
  }

3.底层引擎最终回调到Dart层, 完成计算渲染回收

void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
    }
    ...
}

3.1 执行buildOwner的buildScope方法

void buildScope(Element context, [VoidCallback callback]) {
    ...
    try {
        ...
        //1.排序
      _dirtyElements.sort(Element._sort);
        ...
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        try {
            //2.遍历rebuild
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
        }
        index += 1;
      }
    } finally {
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      //3.清空
      _dirtyElements.clear();
        ...
    }
  }
3.1.1 按照Element的深度从小到大,对_dirtyElements进行排序
3.1.2 遍历执行_dirtyElements当中element的rebuild方法

遍历执行的过程中,也有可能会有新的element被加入到_dirtyElements集合中,此时会根据dirtyElements集合的长度判断是否有新的元素进来了,如果有,就重新排序。

void rebuild() {
   
    if (!_active || !_dirty)
      return;

    Element debugPreviousBuildTarget;

    performRebuild();
  }

element的rebuild方法最终会调用performRebuild(),而performRebuild()不同的Element有不同的实现

执行performRebuild()

performRebuild()不同的Element有不同的实现,我们暂时只看最常用的两个Element:

3.1.3 遍历结束之后,清空dirtyElements集合

3.2 执行WidgetsBinding 的drawFrame (), PipelineOwner对RenderObject管理, 更新页面

@protected
  void drawFrame() {
    pipelineOwner.flushLayout();  //布局需要被布局的RenderObject
    pipelineOwner.flushCompositingBits(); // 判断layer是否变化
    pipelineOwner.flushPaint();  //绘制需要被绘制的RenderObject
    renderView.compositeFrame(); // this sends the bits to the GPU 将画好的layer传给engine绘制
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. 一些语义场景需要
  }

3.3 执行了buildOwner.finalizeTree()清理

void finalizeTree() {
    Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
    try {
      lockState(() {
        _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
      });
     ...
    } catch (e, stack) {
      _debugReportException('while finalizing the widget tree', e, stack);
    } finally {
      Timeline.finishSync();
    }
  }

所有没用的element都调用了deactivateChild方法进行回收

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

也就在这里将被废弃的element添加到了_inactiveElements当中。

另外在废弃element之后,调用inflateWidget创建新的element时,还调用了_retakeInactiveElement尝试通过GlobalKey复用element,此时的复用池也是在_inactiveElements当中。

如果你没有在一帧里通过GlobeKey完成Element的复用,_inactiveElements在最后将被清空,就没办法在复用了。

updateChildren详细过程

  @protected
  List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> forgottenChildren }) {

    Element replaceWithNullIfForgotten(Element child) {
      return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
    }

    int newChildrenTop = 0;
    int oldChildrenTop = 0;
    int newChildrenBottom = newWidgets.length - 1;
    int oldChildrenBottom = oldChildren.length - 1;

    final List<Element> newChildren = oldChildren.length == newWidgets.length ?
        oldChildren : List<Element>(newWidgets.length);

    Element previousChild;

    // Update the top of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      final Widget newWidget = newWidgets[newChildrenTop];
     
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      final Element newChild = updateChild(oldChild, newWidget, previousChild);
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // Scan the bottom of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
      final Widget newWidget = newWidgets[newChildrenBottom];
      
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      oldChildrenBottom -= 1;
      newChildrenBottom -= 1;
    }

    // Scan the old children in the middle of the list.
    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
    Map<Key, Element> oldKeyedChildren;
    if (haveOldChildren) {
      oldKeyedChildren = <Key, Element>{};
      while (oldChildrenTop <= oldChildrenBottom) {
        final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
        
        if (oldChild != null) {
          if (oldChild.widget.key != null)
            oldKeyedChildren[oldChild.widget.key] = oldChild;
          else
            deactivateChild(oldChild);
        }
        oldChildrenTop += 1;
      }
    }

    // Update the middle of the list.
    while (newChildrenTop <= newChildrenBottom) {
      Element oldChild;
      final Widget newWidget = newWidgets[newChildrenTop];
      if (haveOldChildren) {
        final Key key = newWidget.key;
        if (key != null) {
          oldChild = oldKeyedChildren[key];
          if (oldChild != null) {
            if (Widget.canUpdate(oldChild.widget, newWidget)) {
              // we found a match!
              // remove it from oldKeyedChildren so we don't unsync it later
              oldKeyedChildren.remove(key);
            } else {
              // Not a match, let's pretend we didn't see it for now.
              oldChild = null;
            }
          }
        }
      }
      
      final Element newChild = updateChild(oldChild, newWidget, previousChild);
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
    }

    // We've scanned the whole list.
    newChildrenBottom = newWidgets.length - 1;
    oldChildrenBottom = oldChildren.length - 1;

    // Update the bottom of the list.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = oldChildren[oldChildrenTop];
      
      final Widget newWidget = newWidgets[newChildrenTop];
      
      final Element newChild = updateChild(oldChild, newWidget, previousChild);
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // Clean up any of the remaining middle nodes from the old list.
    if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
      for (Element oldChild in oldKeyedChildren.values) {
        if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
          deactivateChild(oldChild);
      }
    }

    return newChildren;
  }
  1. 从前往后, 依次判断oldChild是否存在(如果是GlobalKey, 则当做不存在处理), 并且新旧节点是否相同, 皆是则执行updateChild对比里面的节点, 返回Element, 赋值给newChildren, 记录下一个没有对比的新旧节点序号; 如果不同, 则跳出循环.
  2. 从后往前, 依次判断oldChild是否存在, 并且新旧节点是否相同, 皆是则记录下一个没有对比的新旧节点序号; 如果不同, 则跳出循环.
  3. 判断是否还有未比较的oldChildren, 如果有, 则获取到所有含有key的节点, 存入oldKeyedChildren, 不包括GlobalKey.
  4. 循环未比较的newChildren, 是否存在key, 存在则对比key是否在oldKeyedChildren中存在, 存在则移除oldKeyedChildren中对于的key, 最后调用updateChild返回Element, 赋值给newChildren.
  5. 再次循环, 把后面具有相同key的数据赋值给newChildren.
  6. 删除多余的oldChildren.
上一篇 下一篇

猜你喜欢

热点阅读