前几篇中介绍了flutter整体框架以及dart语言基础和特性。从这篇开始分享flutter的相关内容。本篇要分享的是flutter框架里的三棵树widget,element,renderObject。将从以下几个问题逐步探究这三棵树:
- widget,element,renderObject对应关系
- 三棵树是如何工作的?
Flutter的理念是一切都是Widget(Everything is Widget)。开发者在开发Flutter app的时候主要都是在写很多Widget。对flutter有所了解的都知道flutter是声明式UI框架,与之对应的是命令式。举个例子:一个页面上有N个TextView,在Android开发中如果我们想给这N个TextView设置文案,那我们通常需要调用这N个TextView的setText方法来设置。而对于声明式UI框架的Flutter,需要将数据变更,重新构建WidgetTree。也就是我们在flutter开发过程中数据变更时做的setState。相信你一定会想到每次setState都会重新构建WidgetTree,应该对性能损耗很大吧。flutter号称高性能的跨平台UI框架,那么是怎么解决这样的问题呢?
我们在使用Widget是都是widget的构造方法,另外我们使用的Widget大部分是继承自 StateLessWidget或者StatefulWidget。我们先看看StateLessWidget里都做了些什么呢?
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key key }) : super(key: key);
/// Creates a [StatelessElement] to manage this widget's location in the tree.
///
/// It is uncommon for subclasses to override this method.
@override
StatelessElement createElement() => StatelessElement(this);
/// Describes the part of the user interface represented by this widget.
@protected
Widget build(BuildContext context);
}
通过这段源码我们不难看出,在createElement 方法里,widget将自己的引用传给了StatelessElement,我们在看下StatelessElement:
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
在这块代码中build()方法将widget跟element关联了起来,也就是widget的build方法持有的buildContext就是element。同时我们也可以发现并没有绘制,布局相关的内容,那只能继续跟进父类ComponentElement,Element了,跟进之后发现我们只能找到get renderObject却并没找到renderObject的创建,但是在Element里有这么一块代码:
/// The render object at (or below) this location in the tree.
///
/// If this object is a [RenderObjectElement], the render object is the one at
/// this location in the tree. Otherwise, this getter will walk down the tree
/// until it finds a [RenderObjectElement].
RenderObject? get renderObject {
RenderObject? result;
void visit(Element element) {
assert(result == null); // this verifies that there's only one child
if (element._lifecycleState == _ElementLifecycle.defunct) {
return;
} else if (element is RenderObjectElement) {
result = element.renderObject;
} else {
element.visitChildren(visit);
}
}
visit(this);
return result;
}
在获取的时候实际上是在element 树上去查找离当前节点最近的RenderObjectElement,也就是说此处返回的RenderObject是RenderObjectElement的element.renderObject 。到这里我们也可以得到一个结论就是:Widget 跟 Element是一一对应的,但是Element跟RenderObject并不是一一对应的。下面我们来看下RenderObjectElement:
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = (widget as RenderObjectWidget).createRenderObject(this); /// 此处会去创建RenderObject
assert(!_renderObject!.debugDisposed!);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
mount方法在生命周期里有讲过,会在页面创建的时候调用,也是在此时renderObject跟RenderObjectElement,RenderObjectWidget也就关联上了,并且RenderObject持有element的引用。那么接下来毫无疑问看看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) { }
void layout(Constraints constraints, { bool parentUsesSize = false })
在RenderObject中很容易就找到了跟布局相关的layout方法,和跟绘制相关的paint方法,从而我们可以得出一个结论:就是RenderObject其实是真正做绘制布局相关操作的对象。
下面我们总结下widget,element,renderObject三者之间的关系:
- widget是面对开发者使用的配置对象,可以通过widget对实际绘制做相关的配置和描述,比价轻量级
- element 是Widget在UI树具体位置的一个实例化对象,是实际处理生命周期,UI树位置相关的对象
- RenderObject是实际布局和绘制的对象
其中widget跟Element是一一对应,但是跟RenderObject并非一一对应,在实际开发中,一般renderObject要少。
(在这里我们也可以思考下,如果没有Widget直接将Element暴漏出去供大家使用会有什么问题呢,少了一层结构会不会更简单呢,为啥要设计成这种结构呢?)
知道了三者之间关系那么我们下面我们继续针对flutter这种架构方式是如何做到优化的?
我们在生命周期篇中有提到在创建或者更新时会执行elemnet的如下方法:
class ComponentElement extends Element{
@override
void performRebuild() {
………
Widget build;
build = build();
………
_child = updateChild(_child, build, slot);
…………
}
}
接下来我们着重看下updateChild方法:
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) { /// 如果新的widget是null
if (child != null) ///child 不是null,那么就将该elemnt从element tree上移除
deactivateChild(child);
return null;
}
///如果新的widget不是null时
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
// When the type of a widget is changed between Stateful and Stateless via
// hot reload, the element tree will end up in a partially invalid state.
// That is, if the widget was a StatefulWidget and is now a StatelessWidget,
// then the element tree currently contains a StatefulElement that is incorrectly
// referencing a StatelessWidget (and likewise with StatelessElement).
//
// To avoid crashing due to type errors, we need to gently guide the invalid
// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
// returns false which prevents us from trying to update the existing element
// incorrectly.
//
// For the case where the widget becomes Stateful, we also need to avoid
// accessing `StatelessElement.widget` as the cast on the getter will
// cause a type error to be thrown. Here we avoid that by short-circuiting
// the `Widget.canUpdate` check once `hasSameSuperclass` is false.
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) { // 两个widget谁同一个的话
// We don't insert a timeline event here, because otherwise it's
// confusing that widgets that "don't update" (because they didn't
// change) get "charged" on the timeline.
if (child.slot != newSlot) /// 如果位置不一样更新element tree的位置
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
if (isTimelineTracked) {
Map debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
assert(() {
if (kDebugMode) {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
'${newWidget.runtimeType}',
arguments: debugTimelineArguments,
);
}
child.update(newWidget);
if (isTimelineTracked)
Timeline.finishSync();
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else { // 如果完全不一样
deactivateChild(child);
assert(child._parent == null);
// The [debugProfileBuildsEnabled] code for this branch is inside
// [inflateWidget], since some [Element]s call [inflateWidget] directly
// instead of going through [updateChild].
newChild = inflateWidget(newWidget, newSlot);
}
} else { // 之前的widget就是null,那么就新inflate一个
// The [debugProfileBuildsEnabled] code for this branch is inside
// [inflateWidget], since some [Element]s call [inflateWidget] directly
// instead of going through [updateChild].
newChild = inflateWidget(newWidget, newSlot);
}
assert(() {
if (child != null)
_debugRemoveGlobalKeyReservation(child);
final Key? key = newWidget.key;
if (key is GlobalKey) {
assert(owner != null);
owner!._debugReserveGlobalKeyFor(this, newChild, key);
}
return true;
}());
return newChild;
}
这里着重看下canUpdate,后面开发对理解widget的更新有帮助
/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// If the widgets have no key (their key is null), then they are considered a
/// match if they have the same type, even if their children are completely
/// different.
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
下面我们用张表来总结上面更新的各种情况:
到这里我们基本把widget,element,renderObject三者的关系,以及如何优化的做了一定的分析。你以为flutter就只做了这些吗,只做了这些就称得上是高效能的UI框架了吗?后面我们会继续分析flutter的layer以及fluttet渲染相关内容。