Flutter Widget框架-渲染原理解析

Flutter Framework

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

视图树

  • Widget => 为Element提供配置信息
  • Element => Flutter创建Element的可见树, 同时持有Widget和RenderObject
  • RenderObject => 渲染树中的一个对象

渲染机制

调用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

  • BindingBase 那些单一服务的混入类的基类
  • GestureBinding framework手势子系统的绑定,处理用户输入事件
  • ServicesBinding 接受平台的消息将他们转换成二进制消息,用于平台与flutter的通信
  • SchedulerBinding 调度系统,用于调用Transient callbacks(Window.onBeginFrame的回调)、Persistent callbacks(Window.onDrawFrame的回调)、Post-frame callbacks(在Frame结束时只会被调用一次,调用后会被系统移除,在Persistent callbacks后Window.onDrawFrame回调返回之前执行)
  • PaintingBinding 绘制库的绑定,主要处理图片缓存
  • SemanticsBinding 语义化层与Flutter engine的桥梁,主要是辅助功能的底层支持
  • RendererBinding 渲染树与Flutter engine的桥梁
  • WidgetsBinding Widget层与Flutter engine的桥梁

持有BuildOwner、PipelineOwner

  • BuildOwner

    BuildOwner是Widget framework的管理类, 该类跟踪哪些小部件需要重新构建,并处理应用于整个小部件树的其他任务,比如管理树的非活动元素列表

  • PipelineOwner

    管理真正需要绘制的View, 对RenderObjectTree中发生变化节点的进行flush操作, 最后交给底层引擎渲染

2. attachRootWidget

  • 1.attachRootWidget(app) 方法创建了Root[Widget](也就是 RenderObjectToWidgetAdapter)

    void attachRootWidget(Widget rootWidget) {
        _renderViewElement = RenderObjectToWidgetAdapter(
          container: renderView,
          debugShortDescription: '[root]',
          child: rootWidget
        ).attachToRenderTree(buildOwner, renderViewElement);
      }
    复制代码
  • 2.紧接着调用attachToRenderTree方法创建了 Root[Element]

    RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement element]) {
        if (element == null) {
          owner.lockState(() {
            element = createElement();  //创建rootElement
            element.assignOwner(owner); //绑定BuildOwner
          });
          owner.buildScope(element, () { //子widget的初始化从这里开始
            element.mount(null, null);  // 初始化子Widget前,先执行rootElement的mount方法
          });
        } else {
          ...
        }
        return element;
      }
    复制代码
  • 3.Root[Element]尝试调用mount方法将自己挂载到父Element上,因为自己就是root了,所以没有父Element,挂空了

    owner.buildScope(element, () { //子widget的初始化从这里开始
        element.mount(null, null);  // 初始化子Widget前,先执行rootElement的mount方法
      });
    复制代码
  • 4.mount的过程中会调用Widget的createRenderObject,创建了 Root[RenderObject]

    void mount(Element parent, dynamic newSlot) {
        _parent = parent; //持有父Element的引用
        _slot = newSlot;
        _depth = _parent != null ? _parent.depth + 1 : 1;//当前节点的深度
        _active = true;
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner; //每个Element的buildOwner,都来自父类的BuildOwner, 这样可以保证一个ElementTree,只由一个BuildOwner来维护
        ...
      }
      
    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        _renderObject = widget.createRenderObject(this);
        attachRenderObject(newSlot);
        _dirty = false;
      }
    复制代码
  • 5.我们将app作为参数传给了Root[Widget](也就是 RenderObjectToWidgetAdapter),app[Widget]也就成了为root[Widget]的child[Widget]

  • 6.调用owner.buildScope,开始执行子Tree的创建以及挂载(与更新流程一致, 见更新)

  • 7.调用createElement方法创建出Child[Element]

    Element inflateWidget(Widget newWidget, dynamic newSlot) {
        final Key key = newWidget.key;
    
        if (key is GlobalKey) {
          final Element newChild = _retakeInactiveElement(key, newWidget);
          if (newChild != null) {
    
            newChild._activateWithParent(this, newSlot);
            final Element updatedChild = updateChild(newChild, newWidget, newSlot);
            return updatedChild;
          }
        }
        final Element newChild = newWidget.createElement();
        newChild.mount(this, newSlot);
        return newChild;
      }
    复制代码
  • 8.调用Element的mount方法,将自己挂载到Root[Element]上,形成一棵树

  • 9.挂载的同时,调用widget.createRenderObject,创建Child[RenderObject]

  • 10.创建完成后,调用attachRenderObject,完成和Root[RenderObject]的链接

    @override
      void attachRenderObject(dynamic newSlot) {
        _slot = newSlot;
        
        _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
        _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
        
        final ParentDataElement parentDataElement = _findAncestorParentDataElement();
        if (parentDataElement != null)
          _updateParentData(parentDataElement.widget);
      }
    复制代码

    RenderObject与父RenderObject的挂载稍微复杂了点。每一个Widget都有一个对应的Element,但Element不一定会有对应的RenderObject。(因为有一些Element是不用来做页面显示的, 像StatelessWidget=>StatelessElement没有对应的RenderObject)所以你的父Element并不一有RenderObject,这个时候就需要向上查找。

    RenderObjectElement _findAncestorRenderObjectElement() {
        Element ancestor = _parent;
        while (ancestor != null && ancestor is! RenderObjectElement)
          ancestor = ancestor._parent;
        return ancestor;
      }
    复制代码

    find方法在向上遍历Element,直到找到RenderObjectElement,RenderObjectElement肯定是有对应的RenderObject了,这个时候在进行RenderObject子父间的挂载。

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:

  • ComponentElement,是StatefulWidget和StatelessElement的父类

    void performRebuild() {
        Widget built;
        try {
          built = build();
        } 
        ...
        try {
          _child = updateChild(_child, built, slot);
        } 
        ...
      }
    复制代码

    build()执行我们复写的StatefulWidget的state的build方法, 拿到子Widget, 交给updateChild

    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    	...
    		//1. 如果newWidget是null, 说明删除控件, Element被删除
        if (newWidget == null) {
          if (child != null)
            deactivateChild(child);
          return null;
        }
        
        if (child != null) {
        	//2. 如果新旧控件相同, 说明Widget复用了, 判断位置是否相同, 不相同更新
          if (child.widget == newWidget) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            return child;
          }
          //3. 判断key值和运行时类型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(这个时候应该是Widget变了, 但是还是同类型的Widget)
          if (Widget.canUpdate(child.widget, newWidget)) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            child.update(newWidget);// 
            return child;
          }
          deactivateChild(child);
        }
        //4. 如果上面的条件都不满足, 创建新的Element
        return inflateWidget(newWidget, newSlot);
      }
    复制代码
    • 1.如果newWidget是null, 说明删除控件, Element被删除

    • 2.如果新旧控件相同, 说明Widget复用了, 判断位置是否相同, 不相同更新

    • 3.判断key值和运行时类型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(这个时候应该是Widget变了, 但是还是同类型的Widget)

      static bool canUpdate(Widget oldWidget, Widget newWidget) {
          return oldWidget.runtimeType == newWidget.runtimeType
              && oldWidget.key == newWidget.key;
        }
      复制代码

      child.update(newWidget);方法, 会根据newWidget的类型执行不同的update方法, 例如:

      • Column是MultiChildRenderObjectWidget类型的, 就会执行下面的方法:

        @override
          void update(MultiChildRenderObjectWidget newWidget) {
        
            super.update(newWidget);
            _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
            _forgottenChildren.clear();
          }
        复制代码

        由于Column里面的孩子是children类型(MultiChildRenderObjectWidget), 有多个, 所以对比算法采用updateChildren, 返回新的Element

      • Container是StatelessWidget类型的, 所以他执行StatelessWidget的update方法:

          @override
          void update(StatelessWidget newWidget) {
            super.update(newWidget);
            _dirty = true;
            rebuild();
          }
        复制代码
      • Scaffold是StatefulWidget类型的, 所以执行:

          @override
          void update(StatefulWidget newWidget) {
        
            super.update(newWidget);
        
            final StatefulWidget oldWidget = _state._widget;
         
            _dirty = true;
            _state._widget = widget;
            try {
              _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
              final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
            } finally {
              _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
            }
            rebuild();
          }
        复制代码

      如果是StatelessWidget/StatefulWidget类型, 则继续执行下一级的对比, 以此类推.(child.update => rebuild => performRebuild(), rebuild就是上面那个方法)

      如果是MultiChildRenderObjectWidget类型, 则updateChildren里面会进行List对比算法, 每一个item也会调用updateChild()方法, 进行计算, 详细过程见最后

    • 4.如果上面的条件都不满足, 创建新的Element

      首先会尝试通过GlobalKey去查找可复用的Element,复用失败就调用Widget的方法创建新的Element,然后调用mount方法,将自己挂载到父Element上去,会在这个方法里创建新的RenderObject。

      Element inflateWidget(Widget newWidget, dynamic newSlot) {
          final Key key = newWidget.key;
          if (key is GlobalKey) {
            final Element newChild = _retakeInactiveElement(key, newWidget);
            if (newChild != null) {
              newChild._activateWithParent(this, newSlot);
              final Element updatedChild = updateChild(newChild, newWidget, newSlot);
              return updatedChild;
            }
          }
          final Element newChild = newWidget.createElement();
          newChild.mount(this, newSlot);
          return newChild;
        }
      复制代码
  • RenderObjectElement,是有渲染功能的Element的父类

    与ComponentElement的不同之处在于,没有去build,而是调用了updateRenderObject方法更新RenderObject。

      @override
      void performRebuild() {
        widget.updateRenderObject(this, renderObject);
        _dirty = false;
      }
    复制代码

    他代表的具有自己渲染功能的一类Widget(Text,没有child的)

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 updateChildren(List oldChildren, List newWidgets, { Set 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 newChildren = oldChildren.length == newWidgets.length ?
        oldChildren : List(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 oldKeyedChildren;
    if (haveOldChildren) {
      oldKeyedChildren = {};
      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.

你可能感兴趣的:(Flutter Widget框架-渲染原理解析)