Flutter-widget渲染原理/流程的探究(原创)

471 阅读6分钟

三棵树的渲染模型

image.png

1. 为什么采用三棵树的渲染模型

在三棵树的结构模型中,前两个树结构(Widget tree和 Eelement Tree)是极其不稳定的,在运行过程基于数据的变化和用户的交互以及动画等,随时会重新构建,而频繁的去操作、创建RenderTree是一个很耗性能的事情,同时并不是所有的Widget都会追踪执行渲染操作,非渲染的Widget最终不会生成对应的RenderObject。flutter通过算法在Widget Tree和Element Tree经常变动的情况下通过定量数据修改、复用等方式保持Render Tree的稳定性,减小性能开销,同时Element Tree肩负着中间层功能,根据不同的Widget需要,判断是否更新或创建RenderObject对象。

2. 三棵树分别肩负着什么功能使命

Widget Tree:

基于当前应用状态来描述UI信息和层级结构

Element Tree:
  • 挂载Widget
  • 挂载state
  • 引用RenderObject
  • 肩负着中间层功能,根据不同的Widget需要,判断是否更新或创建RenderObject对象
Render Tree:

主要职责是负责是负责视图渲染

3. 三棵树是什么时候创建的,创建流程是怎么样的

Widget层面我们常接触到的一共有3种Widget

  • statefulWidget
  • StatelessWidget
  • RenderObjectWidget

我们先从StatelessWidget入手来分析一下源码: statelessWidget中有一个createElement方法,这个方法返回了一个StatelessElement对象,这个方法在每次视图需要更新/添加/移除Widget的时候就会被调用(updateChild),在调用这个方法的同时将statelessWidget作为参数传入了进入。至此,三棵树中的第二个重要元素(Element)在此被创建。接下来我们基于updateChild看一下一些主要逻辑的调用链

  • 调用链:我们开发的statelessWidget内容何时被创建

Element.updateChild->Element.inflateWidget->Element.mount->子类ComponentElement.mount->ComponentElement._firstBuild->ComponentElement.rebuild->ComponentElement.performRebuild->ComponentElement.build->子类StatelessElement.build

从调用链中我们可以知道,在updateChild这个入口中最终会调用StatelessElement中的build方法,而这个方法就是我们熟悉的,在开发过程中构建UI的build方法,statelessWidget本身是不承载UI内容信息的,是他的build方法返回的内容才会承载UI的描述信息。至此statelessWidget的内容也被调用返回。

接着我们再从statefulWidget入手来分析一下: 同样在StatefulWidget中的createElement中创建了一个StatefulElement,我们看一下StatefulElement的构造方法

其中有这样几句

_state = widget.createState()
state._element = this;
state._widget = widget;

点到为止,自行领悟

StatefulElement同样继承自ComponentElement但在StatefulElement不同的是他自己实现的build方法↓

@override
Widget build() => state.build(this);

点到为止,大家都是成年人了。[手动狗头]

至此相信大家已经明白了以下问题:

  • 为什么在state内部可以使用widget.XXX

接下来我们研究RenderObjectWidget RenderObjectWidget是个抽象类,它的createElement是子类实现的,我们看一下他的子类Padding->SingleChildRenderObjectWidget.createElement 返回的是一个SingleChildRenderObjectElement,接着看SingleChildRenderObjectElement->RenderObjectElement.mount中有一句

_renderObject = (widget as RenderObjectWidget).createRenderObject(this);

而基类Widget是个抽象类他的createRenderObject由子类实现,我们回到子类Padding中会发现这个方法的实现

@override
RenderPadding createRenderObject(BuildContext context) {
  return RenderPadding(
    padding: padding,
    textDirection: Directionality.maybeOf(context),
  );
}

根据继承关系RenderPadding->RenderShiftedBox->RenderBox->RenderObject可以发现这里返回的就是RenderObject,至此我们的RenderObject也被创建。 相信你已经理解了以下问题了:

  • 三棵树是什么时候创建的,创建流程是怎么样
  • state widget Element randerObject他们之间的关系是怎么样的

4. 为什么说RenderObject才是真正的渲染对象/渲染层

首先请看官方文档说明:

image.png

当然我们也不能仅凭官方一句话我们就认,作为社会主义接班人,我们一定要秉持唯物主义精神,一起去源码中找一些蛛丝马迹。 首先,经过我的实践,我们无法从Widget、Element中找到任何和约束、布局、尺寸计算、视图渲染等相关的代码。通过官方文档我们知道,flutter布局的逻辑是自上而下+自下而上的两次遍历确定最终渲染的尺寸的,涉及到渲染逻辑的对象一定会存在尺寸计算的逻辑,然后我们去RenderObject搜寻一番,就会发现满眼皆是和渲染相关的,特别是↓

/// Paint this render object into the given context at the given offset.
///
/// Subclasses should override this method to provide a visual appearance
/// for themselves. The render object's local coordinate system is
/// axis-aligned with the coordinate system of the context's canvas and the
/// render object's local origin (i.e, x=0 and y=0) is placed at the given
/// offset in the context's canvas.
///
/// Do not call this function directly. If you wish to paint yourself, call
/// [markNeedsPaint] instead to schedule a call to this function. If you wish
/// to paint one of your children, call [PaintingContext.paintChild] on the
/// given `context`.
///
/// When painting one of your children (via a paint child function on the
/// given context), the current canvas held by the context might change
/// because draw operations before and after painting children might need to
/// be recorded on separate compositing layers.
void paint(PaintingContext context, Offset offset) { }

仔细看看注释,就已经很明确告诉你了,这玩意就是用来处理视图渲染的。

5.为什么更新数据需要setstate

不废话,直接上源码

@protected
void setState(VoidCallback fn) {
  assert(fn != null);
  assert(() {
    if (_debugLifecycleState == _StateLifecycle.defunct) {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('setState() called after dispose(): $this'),
        ErrorDescription(
          'This error happens if you call setState() on a State object for a widget that '
          'no longer appears in the widget tree (e.g., whose parent widget no longer '
          'includes the widget in its build). This error can occur when code calls '
          'setState() from a timer or an animation callback.',
        ),
        ErrorHint(
          'The preferred solution is '
          'to cancel the timer or stop listening to the animation in the dispose() '
          'callback. Another solution is to check the "mounted" property of this '
          'object before calling setState() to ensure the object is still in the '
          'tree.',
        ),
        ErrorHint(
          'This error might indicate a memory leak if setState() is being called '
          'because another object is retaining a reference to this State object '
          'after it has been removed from the tree. To avoid memory leaks, '
          'consider breaking the reference to this object during dispose().',
        ),
      ]);
    }
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('setState() called in constructor: $this'),
        ErrorHint(
          'This happens when you call setState() on a State object for a widget that '
          "hasn't been inserted into the widget tree yet. It is not necessary to call "
          'setState() in the constructor, since the state is already assumed to be dirty '
          'when it is initially created.',
        ),
      ]);
    }
    return true;
  }());
  final Object? result = fn() as dynamic;
  assert(() {
    if (result is Future) {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('setState() callback argument returned a Future.'),
        ErrorDescription(
          'The setState() method on $this was called with a closure or method that '
          'returned a Future. Maybe it is marked as "async".',
        ),
        ErrorHint(
          'Instead of performing asynchronous work inside a call to setState(), first '
          'execute the work (without updating the widget state), and then synchronously '
          'update the state inside a call to setState().',
        ),
      ]);
    }
    // We ignore other types of return values so that you can do things like:
    //   setState(() => x = 3);
    return true;
  }());
  _element!.markNeedsBuild();
}

↑重点看:_element!.markNeedsBuild();是不是很熟悉

void markNeedsBuild() {
  assert(_lifecycleState != _ElementLifecycle.defunct);
  if (_lifecycleState != _ElementLifecycle.active) {
    return;
  }
  assert(owner != null);
  assert(_lifecycleState == _ElementLifecycle.active);
  assert(() {
    if (owner!._debugBuilding) {
      assert(owner!._debugCurrentBuildTarget != null);
      assert(owner!._debugStateLocked);
      if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
        return true;
      }
      if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
        final List<DiagnosticsNode> information = <DiagnosticsNode>[          ErrorSummary('setState() or markNeedsBuild() called during build.'),          ErrorDescription(            'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '            'is already in the process of building widgets. A widget can be marked as '            'needing to be built during the build phase only if one of its ancestors '            'is currently building. This exception is allowed because the framework '            'builds parent widgets before children, which means a dirty descendant '            'will always be built. Otherwise, the framework might not visit this '            'widget during this build phase.',          ),          describeElement('The widget on which setState() or markNeedsBuild() was called was'),        ];
        if (owner!._debugCurrentBuildTarget != null) {
          information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
        }
        throw FlutterError.fromParts(information);
      }
      assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
    } else if (owner!._debugStateLocked) {
      assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
        ErrorDescription(
          'This ${widget.runtimeType} widget cannot be marked as needing to build '
          'because the framework is locked.',
        ),
        describeElement('The widget on which setState() or markNeedsBuild() was called was'),
      ]);
    }
    return true;
  }());
  if (dirty) {
    return;
  }
  _dirty = true;
  owner!.scheduleBuildFor(this);
}

↑重点看

if (dirty) { return; }

_dirty = true;

owner!.scheduleBuildFor(this); 在这里就重新标记了这个Element需要渲染。

打完收工,愿人间美好,愿世界和平,愿天下再无互卷的程序员。

image.png

本文讨论范围:

  1. 为什么采用三棵树的渲染模型
  2. 三棵树分别肩负着什么功能使命
  3. 三棵树是什么时候创建的,创建流程是怎么样的
  4. 为什么在state内部可以使用widget.XXX
  5. 为什么说RenderObject才是真正的渲染对象/渲染层
  6. 为什么更新数据需要setstate
  7. state widget Element randerObject他们之间的关系是怎么样的