深入Flutter的Rendering层(一)--- 从runApp到三棵树的构建
作者:互联网
本文所有源码版本为Flutter 1.9.1,部分源码会删除assert和debug部分
转载请注明出处,谢谢
#0、本系列文章
#1、前言
使用Flutter的Widget写了一段时间,用是挺好用,但就是越写心里越没底,感到疑惑的问题越来越多,例如:
- 我写的Widget是怎么渲染出来呢?
- 为什么有时候Container是撑满父亲,有时候又不是?
- 我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
- 我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
- 那个渲染溢出(overflow)的错误提示好烦啊,它是怎么出来的?
- …
如果你也有这些问题,本系列文章可以解答以上所有疑惑。本系列会沿着Widget层,往更深的Rendering层,从源码+一些个人理解,清晰地分析从Widget到渲染整个链路。
在分析过程中,我们也会接触到很多Rendering层才接触到的概念,例如RenderView、Layer、PaintingContext、PipelineOwner、BuildOwner、Window等等,这些概念会在解析过程中结合代码逐一说明。
#2、分层
下图是简化后的Flutter分层架构图:
一般Flutter开发时,只会用到上图中的Widgets以及其上面的Material & Cupertino,只做开发的话熟悉这块就足够了。但是如果想深入学习其原理,还需要往底层研究。本系列会大部分地涉猎到Rendering层的代码,也会少量地关联到dart:ui。
关于这个图的更多说明,请看这篇文章:https://www.jianshu.com/p/8e714a204898
#3、关于Widget、Element和RenderObject
我们都知道Widget都有一个对应的Element和RenderObject。Widget类里有一个必须实现的方法是createElement,通过这个方法我们就可以知道Widget对应的Element是哪个;
那么Widget对应的RenderObject呢?
从上图可以知道:
- 展示型Widget都是继承于RenderObjectWidget,而每个RenderObjectWidget都必须实现createRenderObject方法用来构建对应的RenderObject。这个就是展示型Widget对应的RenderObject
- 而我们平时写的Widget还有很多现成的Widget(如Container、Text)都是组合型Widget(Stateless和Stateful)。如果追踪源码你会发现组合型Widget的build方法中会构建真正的RenderObjectWidget。因此对于组合型Widget,它对应的RenderObject就是这个RenderObjectWidget构建的RenderObject。
同样,下文所说的Widget对应的Element和RenderObject就是上述的情况。
下面就从源码分析Rendering层。
#4、从runApp切入
“我写的Widget是怎么渲染出来呢?” — 接下来我们会围绕着这个问题去分析源码。
其实网上也有不少分析Flutter应用启动的高质量文章,如这篇http://gityuan.com/2019/06/29/flutter_run_app/
本文也有类似分析流程,但是会更多关注渲染层,并且一些相关的概念也会更深说明解释。
##4.1 入口
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Container(),
);
}
}
这是最常见的Flutter应用代码,其中MyApp Widget就是我们写的Widget。
##4.2 runApp
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized() //[见小节4.3]
..attachRootWidget(app) //[见小节4.4]
..scheduleWarmUpFrame(); //[见小节4.5]
}
可以看到runApp只要调用了三个函数,下面都有详细分析。
##4.3 ensureInitialized
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
abstract class BindingBase {
BindingBase() {
...
initInstances();
...
}
}
WidgetsFlutterBinding继承抽象类BindingBase,并且有7个mixin,因此在构造函数时会调用最后一个mixin类的initInstances()方法。同时由于每个mixin类都会调用super.initInstances(),所以最终会按倒序调用所有mixin类的initInstances(),最先调用是WidgetsBinding ,最后是GestureBinding。
###4.3.1 时序图
下面是ensureInitialized()方法的时序图,里面只画出了渲染相关的函数调用:
###4.3.2 RendererBinding.initInstances
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner( // 注释①
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window // 注释②
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView(); // [见小节4.3.3]
_handleSemanticsEnabledChanged();
assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback); // 注释③
_mouseTracker = _createMouseTracker();
}
}
####注释①
构建一个PipelineOwner对象,并且传递一些回调函数作为入参。那么问题来了,PipelineOwner是什么东西?先看看源码的注释:
/// The pipeline owner manages the rendering pipeline.
源码注释很长,这里只截取了关键部分。从说明来看可以知道它是管理渲染通道的,那么怎么管理呢?可以看看调用PipelineOwner最核心的代码:
[源码路径:flutter/lib/src/rendering/binding.dart]
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout(); // 触发RenderObject执行布局
pipelineOwner.flushCompositingBits(); //绘制之前的预处理操作
pipelineOwner.flushPaint(); // 触发RenderObject执行绘制
renderView.compositeFrame(); // 将需要绘制的比特数据发给GPU
pipelineOwner.flushSemantics(); // 发送语义化给系统,用于辅助功能等
}
从函数名drawFrame大概就可以知道PipelineOwner的核心作用就是将Widget对应RenderObject的布局绘制结果发送给GPU。这个方法会在下一篇文章进行分析。
一个Flutter App只有这里构建了PipelineOwner,全局只有一个PipelineOwner实例。
####注释②
这里使用了window变量,这个变量其实是dart:ui库暴露出来的一个全局变量,其类型为Window。同样看看源码注释:
[源码路径:flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart]
/// The most basic interface to the host operating system's user interface.
可以看出,Window是Flutter Framework连接宿主操作系统的接口。这句话有点抽象,看看它部分定义就能大概知道该类的作用:
class Window {
// 当前设备的DPI,即一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
// DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5
double get devicePixelRatio => _devicePixelRatio;
// Flutter UI绘制区域的大小
Size get physicalSize => _physicalSize;
// 当前系统默认的语言Locale
Locale get locale;
// 当前系统字体缩放比例。
double get textScaleFactor => _textScaleFactor;
// 当绘制区域大小改变回调
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale发生变化回调
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系统字体缩放变化回调
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
FrameCallback get onBeginFrame => _onBeginFrame;
// 绘制回调
VoidCallback get onDrawFrame => _onDrawFrame;
// 点击或指针事件回调
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
// 此方法会直接调用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 发送平台消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平台通道消息处理回调
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它属性及回调
}
Window类包含了当前设备和系统的一些信息以及Flutter Engine的一些回调。
- 上文参考:https://book.flutterchina.club/chapter14/flutter_app_startup.html
- Window类是连接Flutter框架层(Dart)与引擎层(C++)的关键类,在框架层中window.dart文件里的一些方法在引擎层的window.cc文件有相对应的方法,比如scheduleFrame()方法。 在window.cc里面通过Window::RegisterNatives()注册了一些框架层与引擎层的方法对应关系;(来自:http://gityuan.com/2019/06/15/flutter_ui_draw/)
####注释③
addPersistentFrameCallback方法估计大家都有用过,就是注册一个持久的监听,当Flutter接收到一个"Vsync"信号时,最终会触发调用注册的PersistentFrameCallback。当然这里也一样。
[源码路径:flutter/lib/src/rendering/binding.dart]
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
因此,注释③这行代码的意思就是,当接收到"Vsync"信号并可以执行下一帧的时候,调用RendererBinding.drawFrame()。该方法的执行内容将在下一篇文章就行分析。
###4.3.3 initRenderView
继续回到源码分析;
[源码路径:flutter/lib/src/rendering/binding.dart]
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration(), window: window);
// 注释①
renderView.scheduleInitialFrame(); // [见小节4.3.4]
}
set renderView(RenderView value) {
assert(value != null);
_pipelineOwner.rootNode = value; // 注释②
}
[源码路径:flutter/lib/src/rendering/object.dart]
AbstractNode get rootNode => _rootNode;
AbstractNode _rootNode;
set rootNode(AbstractNode value) {
if (_rootNode == value)
return;
_rootNode?.detach();
_rootNode = value;
_rootNode?.attach(this); // 注释③
}
####注释①
这里创建了一个RenderView实例。你没猜错,这个实例也是全局只有一个的。那么RenderView是啥玩意呢?
[源码路径:/flutter/lib/src/rendering/view.dart]
/// The root of the render tree.
///
/// The view represents the total output surface of the render tree and handles
/// bootstrapping the rendering pipeline. The view has a unique child
/// [RenderBox], which is required to fill the entire output surface.
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
RenderView({
RenderBox child,
@required ViewConfiguration configuration,
@required ui.Window window,
}) : assert(configuration != null),
_configuration = configuration,
_window = window {
this.child = child;
}
}
注释第一行已经表明身份,没错它就是我们RenderObject树的根节点,它也是集成于RenderObject。我们写的Widget对应的RenderObject就是挂在这个RenderView下,就是它的那个child属性(不是在这里挂上去的,挂上去的代码在下一节)
一个Flutter App只有这里构建了RenderView,当然全局只有一个RenderView实例。
####注释②
注释①赋值renderView会触发调用setter注释②,同样注释②也会触发调用setter注释③。
####注释③
[源码路径:flutter/lib/src/foundation/node.dart]
@mustCallSuper
void attach(covariant Object owner) {
assert(owner != null);
assert(_owner == null);
_owner = owner;
}
整个流程下来,可以整理为pipelineOwner的rootNode是renderView,而renderView也不客气地attach了pipelineOwner。简单来说就是互相持有对方的引用。
###4.3.4 scheduleInitialFrame
[源码路径:/flutter/lib/src/rendering/view.dart]
/// Bootstrap the rendering pipeline by scheduling the first frame.
///
/// This should only be called once, and must be called before changing
/// [configuration]. It is typically called immediately after calling the
/// constructor.
void scheduleInitialFrame() {
assert(owner != null);
assert(_rootTransform == null);
scheduleInitialLayout(); // [见4.3.5]
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); // [见4.3.6和4.3.7]
assert(_rootTransform != null);
owner.requestVisualUpdate(); // [见4.3.8]
}
###4.3.5 scheduleInitialLayout
[源码路径:flutter/lib/src/rendering/object.dart]
/// Bootstrap the rendering pipeline by scheduling the very first layout.
///
/// Requires this render object to be attached and that this render object
/// is the root of the render tree.
///
/// See [RenderView] for an example of how this function is used.
void scheduleInitialLayout() {
assert(attached);
assert(parent is! RenderObject);
assert(!owner._debugDoingLayout);
assert(_relayoutBoundary == null);
_relayoutBoundary = this; // 注释①
assert(() {
_debugCanParentUseSize = false;
return true;
}());
owner._nodesNeedingLayout.add(this); // 注释②
}
####注释①
设置relayoutBoundary为自己。relayoutBoundary是在layout布局过程中用的,relayoutBoundary顾名思义就是布局的界限。如果一个 RenderObject 是 relayoutBoundary,就表示它的大小变化不会影响到 parent 的大小了,于是 执行重新布局时,就从该RenderObject开始重新layout,parent 不用重新布局了。【该具体逻辑分析将会在下一篇文章从源码侧说明。】
####注释②
PipelineOwner有个List属性_nodesNeedingLayout,在这里的RenderObject会在PipelineOwner执行flushLayout时,拿出来去执行layout方法。【该具体逻辑分析将会在下一篇文章从源码侧说明。】
###4.3.6 _updateMatricesAndCreateNewRootLayer
[源码路径:flutter/lib/src/rendering/view.dart]
Layer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
assert(_rootTransform != null);
return rootLayer;
}
这个函数主要创建了一个TransformLayer实例。TransformLayer继承与Layer,而Layer的源码注释如下:
[源码路径:flutter/lib/src/rendering/layer.dart]
/// A composited layer.
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.
再看看它的关键调用代码:
[源码路径:flutter/lib/src/rendering/view.dart]
/// Uploads the composited layer tree to the engine.
///
/// Actually causes the output of the rendering pipeline to appear on screen.
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder); // 这个layer就是上面创建的TransformLayer
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
assert(() {
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
return true;
}());
} finally {
Timeline.finishSync();
}
}
从这两处代码和注释可以大概理解到Layer其实就是RenderObject/Render树绘制出来的内容数据。当然Layer还有很多别的重要知识点,【同样将会在下一篇文章从源码侧说明。】
###4.3.7 scheduleInitialPaint
[源码路径:flutter/lib/src/rendering/object.dart]
/// Bootstrap the rendering pipeline by scheduling the very first paint.
///
/// Requires that this render object is attached, is the root of the render
/// tree, and has a composited layer.
///
/// See [RenderView] for an example of how this function is used.
void scheduleInitialPaint(ContainerLayer rootLayer) {
assert(rootLayer.attached);
assert(attached);
assert(parent is! RenderObject);
assert(!owner._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layer == null);
_layer = rootLayer;
assert(_needsPaint);
owner._nodesNeedingPaint.add(this);
}
这里就干了两件事
- 把刚才创建的TransformLayer赋值给_layer,而这个_layer就是最终执行compositeFrame方法用的layer,该方法会把layer的数据通过Window类渲染出来。
- PipelineOwner还有个List属性_nodesNeedingPaint,在这个List里的RenderObject会在PipelineOwner执行flushPaint时,拿出来去执行paint方法进行绘制。【该具体逻辑分析将会在下一篇文章从源码侧说明。】
###4.3.8 requestVisualUpdate
[源码路径:flutter/lib/src/rendering/object.dart]
/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
///
/// Used to notify the pipeline owner that an associated render object wishes
/// to update its visual appearance.
void requestVisualUpdate() {
if (onNeedVisualUpdate != null)
onNeedVisualUpdate(); // 回调onNeedVisualUpdate方法
}
[源码路径:flutter/lib/src/rendering/binding.dart]
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate, // onNeedVisualUpdate其实就是调用ensureVisualUpdate
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
...
}
}
[源码路径:flutter/lib/src/scheduler/binding.dart]
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame(); // 最终会调用scheduleFrame
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
跟着注释说明,最终会调用scheduleFrame方法,而该方法从名字大概就可以知道它会触发调度下一帧渲染逻辑,最终就是触发drawFrame方法。
###4.3.9 ensureInitialized小结
至此,WidgetsFlutterBinding.ensureInitialized()已经分析了七七八八,我们总结一下,这一步还是做了不少事情。
- 这一步主要是初始化了整个RenderObject树到渲染通道的框架。
- 首先创建了PipelineOwner实例,通过PipelineOwner去管理渲染通道的执行流程;然后创建了RenderView实例,并且绑定了PipelineOwner实例,而Window实例也在RenderView关联起来。最后,为RenderView构建了一个绘制的载体TransformLayer,然后触发调度下一帧scheduleFrame。
- 这些串在一起就是,RenderView的内容(也就是我们写的Widget对应RenderObject内容)绘制在TransformLayer上,然后通过PipelineOwner管理整个渲染调度流程,把TransformLayer上绘制好的内容数据,通过Window类发送到更底层/GPU渲染出来。
##4.4 attachRootWidget
接下来分析runApp的第二步,WidgetsFlutterBinding.attachRootWidget。它其实是调用了mixin的WidgetsBinding.attachRootWidget()。
我已经整理了这一步的大致时序图。
###3.4.1 时序图
我们跟着时序图一步一步分析。
###4.4.2 BuildOwner
[源码路径:flutter/lib/src/widgets/binding.dart]
/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner;
final BuildOwner _buildOwner = BuildOwner();
又是一个新事物。在WidgetsBinding里有个字段构建了一个BuildOwner实例,从注释得知,它主要管理Widget树的构建build。描述依然非常抽象,从其他核心调用,我们大概可以知道Widget的build、mount和unmount等都跟它有关联。
一个Flutter App全局只有一个BuildOwner实例。
###4.4.3 attachRootWidget
[源码路径:flutter/lib/src/widgets/binding.dart]
/// Takes a widget and attaches it to the [renderViewElement], creating it if
/// necessary.
///
/// This is called by [runApp] to configure the widget tree.
///
/// See also [RenderObjectToWidgetAdapter.attachToRenderTree].
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( // 注释①
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement); // [见4.4.4]
}
####注释①
这里构造了一个RenderObjectToWidgetAdapter实例,它继承于Widget,所以它本质就是Widget。然后我们写的Widget(也就是rootWidget变量)作为child参数传进去,而Render树的根结点RenderView也作为container参数传进去。
从这些可以看出来这个RenderObjectToWidgetAdapter有多厉害,没错,它就是我们Widget树的根结点,我们写的Widget就是挂在它下面,它对应的RenderObject就是RenderView。
###4.4.4 attachToRenderTree
[源码路径:flutter/lib/src/widgets/binding.dart]
/// Inflate this widget and actually set the resulting [RenderObject] as the
/// child of [container].
///
/// If `element` is null, this function will create a new element. Otherwise,
/// the given element will have an update scheduled to switch to this widget.
///
/// Used by [runApp] to bootstrap applications.
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() { // lockState,在下面代码执行过程中,禁止调用setState方法
element = createElement(); // 构建一个Element实例,见[4.4.5]
assert(element != null);
element.assignOwner(owner); // 将上面介绍的BuildOwner赋值给Element实例
});
owner.buildScope(element, () { // 见[4.4.6]
element.mount(null, null); // 作为一个回调函数传给buildScope,见[4.4.6]
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
这里值得一提的是,入参element,也就是renderViewElement这个时候还是为null,所以会走第一个判断逻辑。当走完这个函数之后,renderViewElement才会被赋值。
###4.4.5 createElement
[源码路径:flutter/lib/src/widgets/binding.dart]
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
构建了一个RenderObjectToWidgetElement实例,它继承于Element。这里的this是刚才的根Widget RenderObjectToWidgetAdapter。
至此,三棵树的根结点都出来了:
- Widget树的根结点是RenderObjectToWidgetAdapter;
- Element树的根结点是RenderObjectToWidgetElement;
- RenderObject树的根结点是RenderView;
他们仨一一对应。
###4.4.6 buildScope
[源码路径:flutter/lib/src/widgets/framework.dart]
void buildScope(Element context, [VoidCallback callback]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineWhitelistArguments);
try {
if (callback != null) {
_dirtyElementsNeedsResorting = false;
callback(); //此处回调就是刚才说的mount() ,见4.4.7
}
...
_dirtyElements.sort(Element._sort);
int dirtyCount = _dirtyElements.length;
while (index < dirtyCount) {
_dirtyElements[index].rebuild(); //对mark为dirty的Element执行rebuild操作,这块的核心会在下一篇文章说明
...
}
} finally {
...
Timeline.finishSync();
}
}
###4.4.7 mount
[源码路径:flutter/lib/src/widgets/binding.dart] RenderObjectToWidgetElement.mount
@override
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot); // 看下面
_rebuild(); // 见4.4.8
}
[源码路径:flutter/lib/src/widgets/framework.dart] RenderObjectElement.mount
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); // 再看下面
_renderObject = widget.createRenderObject(this); // 注释①
assert(() { _debugUpdateRenderObjectOwner(); return true; }());
assert(_slot == newSlot);
attachRenderObject(newSlot); // 本次分析不涉及这里
_dirty = false;
}
[源码路径:flutter/lib/src/widgets/framework.dart] Element.mount
// 这里是每个Element必经之路
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
assert(slot == null);
assert(depth == null);
assert(!_active);
_parent = parent; // 赋值爸妈
_slot = newSlot; // slot参数,可以理解为是一个存数据的地方
_depth = _parent != null ? _parent.depth + 1 : 1; // 深度
_active = true; // 这个不用说吧
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner; // 这个就是BuildOwner,所以刚才创建的BuildOwner会通过这种方式为整棵树每一个结点都赋值上;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this); // 注册并建立globalKey和Element的关联
}
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
}
####注释①
widget.createRenderObject()这个方法很重要。
这里就是我们写的Widget创建出RenderObject的地方,然后Element就可以存有RenderObject实例。createRenderObject是每一个RenderObjectWidget必须实现了,看看源码就可以知道每个Widget它对应的RenderObject是什么类别。
那么Element是怎样跟Widget关联起来呢?后面会介绍。(见4.4.10注释②)
那么我们的根Widget RenderObjectToWidgetAdapter是怎么实现createRenderObject方法:
@override RenderObjectWithChildMixin<T> createRenderObject(BuildContext > context) => container;
而这里返回的container就是刚才我们用RenderView去赋值的。所以RenderView就是这里与RenderObjectToWidgetElement关联在一起。
###4.4.8 _rebuild
[源码路径:flutter/lib/src/widgets/binding.dart]
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot); // 见4.4.9
} catch (exception, stack) {
...
}
}
这里的widget就是根结点RenderObjectToWidgetAdapter,而widget.child就是我们写的Widget。因此这里就是把我们的Widget生成对应的Element,然后挂在根Element的child属性里。
###4.4.9 updateChild
[源码路径:flutter/lib/src/widgets/framework.dart]
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
...
return child;
}
deactivateChild(child);
}
return inflateWidget(newWidget, newSlot); // 见4.4.10
}
重要!!!
这个方法是Flutter Rendering层非常重要的一个函数。它在Widget的增删改过程都会触发调用。什么会触发Widget树的增删改?最简单的栗子就是初次应用启动,或者setState的时候;
下面是源码注释:
/// Update the given child with the given new configuration.
///
/// This method is the core of the widgets system. It is called each time we
/// are to add, update, or remove a child based on an updated configuration.
///
/// If the `child` is null, and the `newWidget` is not null, then we have a new
/// child for which we need to create an [Element], configured with `newWidget`.
///
/// If the `newWidget` is null, and the `child` is not null, then we need to
/// remove it because it no longer has a configuration.
///
/// If neither are null, then we need to update the `child`'s configuration to
/// be the new configuration given by `newWidget`. If `newWidget` can be given
/// to the existing child (as determined by [Widget.canUpdate]), then it is so
/// given. Otherwise, the old child needs to be disposed and a new child
/// created for the new configuration.
///
/// If both are null, then we don't have a child and won't have a child, so we
/// do nothing.
///
/// The [updateChild] method returns the new child, if it had to create one,
/// or the child that was passed in, if it just had to update the child, or
/// null, if it removed the child and did not replace it.
///
/// The following table summarizes the above:
///
/// | | **newWidget == null** | **newWidget != null** |
/// | :-----------------: | :--------------------- | :---------------------- |
/// | **child == null** | Returns null. | Returns new [Element]. |
/// | **child != null** | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
这里主要是处理Element能不能复用的逻辑,我们都知道Element创建很好资源,通过复用可以大大减少耗时。
具体的处理逻辑是:
- 如果新的Widget为null,那么就不需要Element,所以就都返回null;
唯一要注意是如果旧的Element还在,需要移除掉Element与WIdget的关联,同时这个Element会保存在BuildOwner一个缓存_inactiveElements中,如果下次能用的时候会直接拿来用,避免创建耗时。(可以看4.4.10注释①) - 如果新的Widget不为null,而旧的Element也不为null,那么需要通过Widget.canUpdate方法判断这个旧的Element能不能复用。而判断逻辑是Element对应的Widget和新Widget的类型和key相同就可以复用。
- 其他情况,就需要通过新的Widget去创建新的Element。具体请看下一小节。
毫无疑问,我们写的Widget肯定会走到情况3,也就是调用inflateWidget创建Element;
###4.4.10 inflateWidget
[源码路径:flutter/lib/src/widgets/framework.dart]
@protected
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;
}
这个方法同样重要。
如果你是Android开发的,那么对inflate肯定熟悉不过。Android里如果要把xml转成View,我们调用的接口就是LayoutInflater.inflate。而Flutter的这个接口意思也是差不多,Widget作为Flutter界面的蓝图,它更接近于xml,所以这个接口就是把Widget描述的样子inflate出来。
####注释①
_retakeInactiveElement这个方法内部会去刚才我提到的_inactiveElements缓存里找有没有能复用Element,然后直接拿来用。
####注释②
newWidget.createElement(),顾名思义,就是创建Widget对应的Element实例。其实Element的构造函数是必须要传Widget作为入参,在这里Element就与Widget进行了关联。
至此,我们已经知道Element是如何关联Widget和RenderObject了。
看看源码,可以了解每个Widget对应的Element和RenderObject类型。
####注释③
mount方法可能每个Element类型都会不一样,但是做的事情的核心就是将自己挂在parent Element上,同时为自己的Widget.child生成Element。
###4.4.11 attachRootWidget小结
runApp的第二步已经分析完成。到这一步很多事情就明朗起来。
- 首先构造Widget树和Element树的根结点,分别是RenderObjectToWidgetAdapter和RenderObjectToWidgetElement,而他们对应的RenderObject就是上一步构造的Render树根结点RenderView;
- 然后把我们写的Widget作为rootWidget通过mount方法挂载到了树的根结点上。到这里我们已经解答“我写的Widget是怎么渲染出来呢?”的一半了。(还有一半下一篇文章进行分析)
同时,最初的这两个问题也已经有了答案:
- 问题:我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
答:Element有高效的复用逻辑,即使是Widget频繁rebuild,但是通过上述的复用操作,可以尽可能减少耗时,提高性能; - 问题:我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
答:分析下来,我们已经很清楚Element在Widget和RenderObject之间的重要性。大家都知道Widget是immutable的,而RenderObject是可以改变而重绘的,这里面就需要Element去从中协调。Element就像一个倒贴的中间商,不仅不赚差价,还帮你做优化。
– Widget通过Element进行mount,当Widget有变化时也是通过Element做更新改变–updateChild;
– Element同时拥有Widget和RenderObject的引用,它们之间的沟通需要经过Element;
###4.5 scheduleWarmUpFrame
终于来到最后一步了,WidgetsFlutterBinding.scheduleWarmUpFrame。
[源码路径:flutter/lib/src/scheduler/binding.dart]
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
assert(_warmUpFrame);
handleBeginFrame(null); // 帧准备绘制
});
Timer.run(() {
assert(_warmUpFrame);
handleDrawFrame(); // 帧绘制
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame's timestamp will typically be far in
// the past (the time of the last real frame), so if we didn't reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the "real" frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
这一步主要就是触发立刻绘制首帧,而不用等待下一个“Vsync”信号。
关于帧绘制handleDrawFrame相关分析,请看下一篇文章。
#5、参考资料
scnuxisan225 发布了20 篇原创文章 · 获赞 42 · 访问量 4万+ 私信 关注
标签:Widget,Element,assert,Rendering,runApp,源码,child,null,Flutter 来源: https://blog.csdn.net/scnuxisan225/article/details/104128264