likes
comments
collection
share

Flutter Framework 渲染流程分析(二):Element

作者站长头像
站长
· 阅读数 18

本文是Flutter Framework 渲染流程分析中的第二篇

  • Flutter Framework 渲染流程分析(一):开篇
  • Flutter Framework 渲染流程分析(二):Element
  • Flutter Framework 渲染流程分析(三):RenderObject
  • Flutter Framework 渲染流程分析(四):Layer
  • Flutter Framework 渲染流程分析(五):常见问题分析

上篇文章讲了WidgetElement中一些关键的属性和方法,粗略聊了一下Element中方法调用的流程。用一张图表示 Flutter Framework 渲染流程分析(二):Element 怎么形成一棵Element Tree?思路其实很简单,Element在挂载自身的时候,生成childElement并让childElement挂载到自己节点下面,childElement挂载时又会触发child节点同样的处理。下面将就通过源码一步步去分析,注意在所有章节中assert或者一些无关紧要的代码都会用...省略掉

mount()

Element底下有两个直接子类ComponentElementRenderObjectElement,因为在方法实现和调用上存在些许差异,为了方便理解,我们取稍简单些的ComponentElement去分析。按照上述的流程图,先看mount的处理

// code 2.1

// Component
void mount(Elment? parent, Object? newSlot) {
    super.mount(parent, newSlot); // code 2.2
    ... 
    _firstBuild();
    ...
}

// code 2.2

// Element
void mount(Element? parent, Object? newSlot) {
    ...
    // 更新一些节点信息
    _parent = parent;   
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
      _owner = parent.owner;
    }
    ...
    // 如果是 GlobalKey,会存到 owner 上,以便后续复用
    final Key? key = widget.key;
    if (key is GlobalKey) {
        owner!._registerGlobalKey(key, this);
    }
    ...
}

code 2.1 的代码很简单,在调用父类的mount方法进行一些必要的更新之外就执行_firstBuild()

_firstBuild()

// code 2.3

// ComponentElement
void _firstBuild() {
    rebuild();
}

ComponentElement 没有重写rebuild() 方法,所以要看父类的处理

rebuild()

// code 2.4

// Element
void rebuild({bool force = false}) {
    // 对状态进等一些判断,减少一些不必要的 build 行为
    if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !_force)) {
        return;
    }
    ...
    try {
        performRebuild();
    } finally {
        ...
    }
}

rebuild()只是对performRebuild()的调用做了一些保护,防止不必要的 build 行为,核心还是要看performRebuild()

performRebuild()

// code 2.5

// ComponenetElement
void performRebuild() {
    Widget? built;
    try {
        // ...
        built = build();
    } catch(e, stack) {
        // build 出错时用来顶替页面布局的占位 Widget
        built = ErrorWidget.builder(...);
    } finnaly {
        super.performRebuild();  // code 2.6,将 _dirty 标记为 false
    }
    try {
        _child = updateChild(_child, built, slot);
        ...
    } catch (e, stack){
        // updateChild 出错时用来顶替页面布局的占位 Widget
        built = ErrorWidget.builder(...);
        _child = updateChild(null, built, slot);
    }
}

// code 2.6

// Element
void perfromRebuild() {
    _dirty = false;
}

build() 方法是不是很熟悉?没错,对于StatelessElement来说,它就是Widget.build();而对于StatefulElement来说,它就是State.build()。这里关键处理就两个:首先是获取到childWidget,也就是built;第二个就是执行updateChild()方法进行 child 的更新操作。

updateChild()

// code 2.7

// ComponentElement
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {

    // newWidget 为 null 时,表示这个节点已经清空了
    if (newWidget == null) {

        if (child != null) {
            deactivateChild(child);
        }
        return null;
    }
    
    final Element newChild;
    if (child != null) {
        // 当前已经有 child element 存在了
        ...
        if(child.widget == newWidget) {
            // 同一个 child,更新 slot 即可。
            
            if (child.slot != newSlot) {
                updateSlotForChild(child, newSlot);
            }
            newChild = child;
        } else if(Widget.canUpdate(child.widget, newWidget)) {
            // 同一类 widget,直接通过 newWidget 走更新操作
            
            if (child.slot != newSlot) {
                updateSlotForChild(child, newSlot);
            }
            ...
            child.update(newWidget);
            ...
            newChild = child;
        } else {
           // 不是同一类 widget,移除当前 element,并创建新的
           
            deactivateChild(child);
            ...
            newChild = inflateWidget(newWidget, newSlot);
        }
    } else {
        // 当前没有 element,走创建
        
        newChild = inflateWidget(newWidget, newSlot);
    }
    ...
    return newChild;
}

updateChild 方法的作用是移除、更新或者创建childElement,移除操作是deactivateChild(child),更新操作是child.update(newWidget),创建操作是inflateWidget(newWidget, newSlot)

我们知道,Element是通过Widget“create”出来的。Widget是基础,当widget为空时,其实就表示这个节点已经不存在了,要执行移除操作;当widget不为空时,再判断是要创建还是要更新。假如当前child已经存在,通过判断child.widgetnewWidget是不是一类,如果是一类,表示可以复用,进行更新操作;如果不是一类,就表示newWidget已经是另外一种类型了,要将当前的element给移除,并生成一个新的element挂载到节点上。

对于更新,也就是child.update(newWidget)方法,ComponentElement没有重写,但它的子类都有override,我们取StatelessElement去了解即可(其余的都大同小异)

update()

// code 2.8

// StatelessElement
void update(StatelessWidget newWidget) {
    super.update(newWidget); // 见 code 2.10
    ...
    rebuild(force: true); // 见 code 2.5
}
// code 2.9,Element 中定义的 update 方法
void update(covariant Widget newWidget) {
    ...
    _widget = widget;
}

代码很简单,就是执行的code 2.4中的rebuild操作。此时又形成了一个循环。

inflateWidget

// code 2.10

// ComponentElement
Element inflateWidget(Widget newWidget, Object? newSlot){
    ...
    try {
        final Key? key = newWidget.key;
        if (key is GlobalKey) {
            // 查看 GlobalKey 是否有缓存,如果有则从缓存里面取
            final Element? newChild = _retakeInactiveElement(key, newWidget);
            if (newChild != null) {
                ...
                newChild._activeWithParent(this, newSlot);
                ...
                // 更新,见 code 2.8
                final Element? updatedChild = updateChild(newChild, newWidget, newSlot);  
                ...
                return updatedChild;
            }
            
        }
        // 创建 element
        final Element newChild = newWidget.createElement();
        ...
        // 挂载 element,code 2.1 中介绍的
        newChild.mount(this, newSlot);
        ...
        return newChild;
        
    } finally {
        ...
    }
}

先判断是否有GlobalKey缓存,如果有缓存则从缓存里面取出element执行update操作;没有则通过createElement()方法创建出newChild之后,执行mount处理。从一开始的挂载自身到这里的挂载child,代码又回到了code 2.1,形成一个循环操作。

总结

所谓挂载(mount),即是为了形成树的节点,根据上一章介绍到的AbstractNode结合code 2.2中的代码,其实核心就是将_parent指向上一个节点。根据上述代码逻辑,对文章开头提到的流程图做个完整性补充 Flutter Framework 渲染流程分析(二):Element