Flutter Widget框架-渲染原理解析

Flutter Framework

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

视图树

  • 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混入了很多的其余的Bindingbash

  • 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、PipelineOwnerapp

  • BuildOwner框架

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

  • PipelineOwneride

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

2. attachRootWidget

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

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

    RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> 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<RenderObjectWidget> 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<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.
相关文章
相关标签/搜索