Flutter的渲染流程
Widget
- 在Flutter中万物皆为
Widget
,构成一棵Widget
树,Widget可以理解为 UI界面的状态描述文件,这些描述文件在我们进行状态改变时会不断的重新build,也就是说Widget树的状态是十分不稳定的,当其状态发生变化时,就需要重新Build,那么Flutter渲染引擎渲染Widget
树是非常损耗性能的;
Element
- Element是Widget的实例,是构成Element树的元素,Element相当于虚拟的DOM,Widge描述和配置子树的样子,而Element实际去配置在Element树中特定的位置。Element最大的意义在于以最小的开销来更新RenderObject;
RenderObject
- 是渲染树上的对象,是渲染库的核心;
- 主要负责布局与绘制,同时也是构成渲染树的元素;
下面给一张图,详细描述了Widget,Element与RenderObject之间的关系:
image.png第一点:Padding
,Row
,Text
,TextField
的继承链路
- 从图中看出
Padding
,Row
,Text
,TextField
四种组件的继承关系,最终都是继承自Widget
,所以说在Flutter中万物皆为Widget; -
Padding
->singleChildRenderObjectWidget
->RenderObjectWidget
->Widget
-
Row
->Flex
->singleChildRenderObjectWidget
->RenderObjectWidget
->Widget
-
Text
->statelessWidget
->Widget
-
TextField
->statefulWidget
->Widget
-
Padding
与Row
属于一类,都是继承自RenderObjectWidget
属于渲染对象组件 -
Text
与TextField
属于一类,没有继承自RenderObjectWidget
- 在
Widget
类中有Element createElement()
抽象方法,其作用是为当前Widget创建一个Element对象,所有继承自Widget
类的组件,都可实现这个方法,为自己创建一个Element对象
,也就是说一个Widget对象必定会有一个对应的Element对象; - 针对
Padding
组件,是在其父类SingleChildRenderObjectWidget
中调用createElement()
方法,返回一个singleChildRenderObjectElement
对象;
第二点:Padding
,Row
,Text
,TextField
调用createElement()
返回Element
对象
- 针对
Padding
组件,是在其父类SingleChildRenderObjectWidget
中调用createElement()
方法,返回一个singleChildRenderObjectElement
对象; - 针对
Row
组件,是在其父类multiChildRenderObjectWidget
中调用createElement()
方法,返回一个mutilChildRenderObjectElement
对象; - 针对
Text
组件,是在其父类statelessWidget
中调用createElement()
方法,返回一个statelessElement
对象; - 针对
TextField
组件,是在其父类statefulWidget
中调用createElement()
方法,返回一个statefulElement
对象; - 这四种组件调用
createElement()
方法,创建的Element对象类型均不同,最终都是继承自Element
,其继承关系如下: -
Padding
:singleChildRenderObjectElement
->RenderObjectElement
->Element
-
Row
:mutilChildRenderObjectElement
->RenderObjectElement
->Element
-
Text
:statelessElement
->ComponmentElement
->Element
-
TextField
:statefulElement
->ComponmentElement
->Element
第三点:Element调用mount方法
- 当Widget的Element创建完成时,系统会自动调用Element的mount方法;
- 针对
Padding
,创建的singleChildRenderObjectElement,调用其mount
方法,实现如下:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
- 内部调用其父类
RenderObjectElement
的mount
方法,实现如下:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = widget.createRenderObject(this);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
-
其中
_renderObject = widget.createRenderObject(this)
即根据Element对象来创建渲染对象,看到这里就明确了Widget
->Element
->RenderObject
之间关系了; -
针对
Row
与Padding
类似,最终也是调用父类RenderObjectElement
的mount
方法,创建对应的渲染对象; -
针对
Text
与TextField
,创建的Element对象statelessElement
与statefulElement
对象,最终都是在其父类ComponmentElement
中调用mount
方法,实现如下:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
- 内部调用
_firstBuild()
方法,实现如下:
void _firstBuild() {
rebuild();
}
- 内部调用
rebuild()
方法,实现如下:
void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!_active || !_dirty)
return;
assert(() {
if (debugOnRebuildDirtyWidget != null) {
debugOnRebuildDirtyWidget(this, _debugBuiltOnce);
}
if (debugPrintRebuildDirtyWidgets) {
if (!_debugBuiltOnce) {
debugPrint('Building $this');
_debugBuiltOnce = true;
} else {
debugPrint('Rebuilding $this');
}
}
return true;
}());
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(owner._debugStateLocked);
Element debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = owner._debugCurrentBuildTarget;
owner._debugCurrentBuildTarget = this;
return true;
}());
performRebuild();
assert(() {
assert(owner._debugCurrentBuildTarget == this);
owner._debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
}());
assert(!_dirty);
}
- 内部的核心方法调用为
performRebuild()
,此方法是一个抽象方法,选中Command+Alt+B
查看实现类有ComponmentElement
,实现如下:
@override
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
_debugDoingBuild = false;
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
_child = updateChild(null, built, slot);
}
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
- 各种断言与逻辑判断,其核心调用为
built = build()
,build()
是ComponmentElement类的一个抽象方法,其子类有statelessElement
和statefulElement
,其实现分别如下:
@override
Widget build() => widget.build(this);
@override
Widget build() => _state.build(this);
- 可以看出无状态的statelessWidget的build方法的调用流程:
Widget创建完Element -> 调用Element的mount方法 -> _firstBuild() - rebuild() -> performRebuild() -> build() -> widget.build(this)
- 有状态的statefulWidget的build方法的调用流程:
Widget创建完Element -> 调用Element的mount方法 -> _firstBuild() - rebuild() -> performRebuild() -> build() -> _state.build(this)
- 这也就解释了有状态的statefulWidget的build方法是在
State
中的; - widget.build(this)与_state.build(this)中的
参数this
,就是Element
,也就是说Widget build(BuildContext context)
,中的BuildContext
本质就是Element
第四点:statefulWidget的底层探索
- statefulWidget创建的Element为
StatefulElement
,其构造方法如下:
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
assert(() {
if (!_state._debugTypesAreRight(widget)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
ErrorDescription(
'The createState function for ${widget.runtimeType} returned a state '
'of type ${_state.runtimeType}, which is not a subtype of '
'State<${widget.runtimeType}>, violating the contract for createState.'
),
]);
}
return true;
}());
assert(_state._element == null);
_state._element = this;
assert(
_state._widget == null,
'The createState function for $widget returned an old or invalid state '
'instance: ${_state._widget}, which is not null, violating the contract '
'for createState.',
);
_state._widget = widget;
assert(_state._debugLifecycleState == _StateLifecycle.created);
}
- 可以看到statefulWidget的State是在
statefulElement
的构造函数中创建的; -
_state._widget = widget
,将Widget绑定给State,这也就解释了在State中可以访问到Widget对象;
第五点:Element的源码探索
-
Element
类的构造方法如下:
Element(Widget widget)
: assert(widget != null),
_widget = widget;
- 可以看出
Element引用了widget
; -
RenderObjectElement
继承自Element
,其内部有访问renderObject
渲染对象的setter与getter方法,如下所示:
@override
RenderObject get renderObject => _renderObject;
RenderObject _renderObject;
- 可以看出
Element引用了renderObject
; - 如果是
statefulElement
,还可以引用State
;
总结:
-
Widget树
是配置信息,状态不断变化中,不稳定;- 内部调用createElement方法,创建与之对应的Element;
-
Element树
是Widget的实例,真正保存Widget结构数据的对象;- Element创建完成之后,由系统的framework调用mount方法,继承自
RenderObjectWidget
会调用createRenderObject方法,创建渲染对象,继承自statelessWidget
与statefulWidget
不会调用createRenderObject方法,最终会调用widget的build方法与state的build方法; - Element对widget与RenderObject以及state都有引用;
- Element创建完成之后,由系统的framework调用mount方法,继承自
-
RenderObject渲染树
是真正的渲染对象;- 内部包含渲染对象的布局与绘制操作;
Widget的属性 Key的应用
- Widget的构造方法会传入一个可选参数Key,如下:
const Widget({ this.key });
- 此
可选参数Key
的作用是什么,现在我们通过案例来探讨一下:
案例代码一 -- StatelessWidget
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatefulWidget {
@override
_SFHomePageState createState() => _SFHomePageState();
}
class _SFHomePageState extends State<SFHomePage> {
final List<String> names = ["1111", "2222", "3333"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: ListView(
children: names.map((name) {
return ListItemLess(name);
}).toList()),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.delete),
onPressed: () {
setState(() {
names.removeAt(0);
});
},
),
);
}
}
class ListItemLess extends StatelessWidget {
final String name;
final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
ListItemLess(this.name);
@override
Widget build(BuildContext context) {
return Container(
child: Text(name,style: TextStyle(color: Colors.white,fontSize: 25)),
height: 80,
color: randomColor,
);
}
}
- 效果图如下所示:
- 当每次点击右下角的按钮时,都会删除第一条数据;
- 现象:每删除一个,剩余的ListItemLess的颜色都会发生变化;
- 原因:删除之后会调用
setState
方法,会重新build,重新build出来的新的ListItemLess会重新生成一个新的随机颜色;
案例代码二 -- StatefulWidget
- 将上面的ListItemLess改成ListItemful继承自StatefulWidget,代码如下:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatefulWidget {
@override
_SFHomePageState createState() => _SFHomePageState();
}
class _SFHomePageState extends State<SFHomePage> {
final List<String> names = ["1111", "2222", "3333"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: ListView(
children: names.map((name) {
return ListItemful(name);
}).toList()),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.delete),
onPressed: () {
setState(() {
names.removeAt(0);
});
},
),
);
}
}
class ListItemful extends StatefulWidget {
final String name;
ListItemful(this.name);
@override
_ListItemfulState createState() => _ListItemfulState();
}
class _ListItemfulState extends State<ListItemful> {
final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
@override
Widget build(BuildContext context) {
return Container(
child: Text(widget.name),
height: 80,
color: randomColor,
);
}
}
- 现象:每删除一个,都是最后一个被删除,剩余的颜色不会变化;
- 删除之前的Widget Tree 与 Element Tree如下所示:
- 删除第一个青色widget,
整个widget树会重建
,而对应的Element树不会重建,Flutter SDK会根据新建widget树上对应位置的新widget与Element树中Element保存引用的旧widget,进行比对,决定当前Element是更新
还是重建
,判断方法为canUpdate
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
- 当新建的widget与Element引用的旧widget的
runtimeType
与key
值都相同时,那么当前的Element只需要更新数据,不需要重建
,可提升性能; - 删除第一个青色widget之后,widget树与element树如下:
- 然后进行新旧widget的比对,将widget与Element树中Element进行遍历比较:
- fulWidget2与element青引用的widget进行比较,发现类型与key(没有设置key)是相同的,那么element青(原来引用fulWidget1)会保留并更新引用fulWidget2,
- fulWidget3与element粉引用的widget进行比较,发现类型与key(没有设置key)是相同的,那么element粉(原来引用fulWidget2)保留并更新引用fulWidget3
- element绿在widget树中没有对应的widget了,直接删除;
案例代码三 -- StatefulWidget传可选参数key
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatefulWidget {
@override
_SFHomePageState createState() => _SFHomePageState();
}
class _SFHomePageState extends State<SFHomePage> {
final List<String> names = ["1111", "2222", "3333"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: ListView(
children: names.map((name) {
return ListItemful(name,key: ValueKey(name));
}).toList()),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.delete),
onPressed: () {
setState(() {
names.removeAt(0);
});
},
),
);
}
}
class ListItemful extends StatefulWidget {
final String name;
// ListItemful(this.name);
ListItemful(this.name,{Key key}) : super(key: key);
@override
_ListItemfulState createState() => _ListItemfulState();
}
class _ListItemfulState extends State<ListItemful> {
final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
@override
Widget build(BuildContext context) {
return Container(
child: Text(widget.name),
height: 80,
color: randomColor,
);
}
}
- 创建
ListItemful
传入可选参数ValueKey(name)
,即key为数据内容,现在再删除item,正常删除,不会出现颜色变化与删除最后一条; - 判断Element更新/重建的逻辑与上面的相同,只不过现在是每一个widget都绑定一个key,重新build之后widget树中的每个
单个widget
与Element树中element所引用的widget进行比对,若存在相同的runtimeType,key,那么这个element就可以重用,无需销毁重建;
- 重建后的widget树,由于key是数据内容,所以在element树中element引用的widget的key都能匹配到,
案例代码四 -- StatefulWidget传可选参数key -- UniqueKey()
-
UniqueKey
是独一无二的,也就是说每调用一次都会生成一个不带重复的key; - 根据上面的原理,ListItemful创建若传入UniqueKey,那么重建后的widget的key肯定在element树中element引用的widget的key都不能匹配到,所以整个Element树都会重建,所以每删除一次,所有item的颜色都会发生变化;
widget可选参数Key的分类
- Key是一个抽象类,其有一个工厂构造器,子类有:
- LocalKey:应用于具有相同父Element的widget进行比较,是diff算法的核心所在;
- GlobalKey:通常使用于某个widget,然后访问其widget本身与state的;
- LocalKey有三个子类:
- ValueKey:我们以特定的值作Widget的key,比如字符串,数字等;
- ObjectKey:以模型对象作为Widget的key;
- UniqueKey:可确保key为唯一的;
GlobalKey
- 先上案例代码,如下所示:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(key: homeKey),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.gesture),
onPressed: (){
},
),
);
}
}
class SFHomeContent extends StatefulWidget {
final String name = "2222";
@override
_SFHomeContentState createState() => _SFHomeContentState();
}
class _SFHomeContentState extends State<SFHomeContent> {
final String message = "1111";
@override
Widget build(BuildContext context) {
return Text(message);
}
void test(){
print("_SFHomeContentState test");
}
}
- 现在要实现,在点击按钮的时候 能访问SFHomeContent的name属性,能访问_SFHomeContentState的message属性,以及调用test方法,可通过
GlobalKey
来实现; -
GlobalKey
类的定义是:abstract class GlobalKey<T extends State<StatefulWidget>> extends Key
,易知GlobalKey
是一个抽象类,从泛型可以看出其本质是一个State, - 通过创建一个
GlobalKey<_SFHomeContentState>
,然后传参给SFHomeContent
,修改后的代码如下:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
final GlobalKey<_SFHomeContentState> homeKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(key: homeKey),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.gesture),
onPressed: (){
print(homeKey.currentState.message);
print(homeKey.currentState.widget.name);
homeKey.currentState.test();
},
),
);
}
}
class SFHomeContent extends StatefulWidget {
final String name = "2222";
SFHomeContent({Key key}) : super(key: key);
@override
_SFHomeContentState createState() => _SFHomeContentState();
}
class _SFHomeContentState extends State<SFHomeContent> {
final String message = "1111";
@override
Widget build(BuildContext context) {
return Text(message);
}
void test(){
print("_SFHomeContentState test");
}
}
网友评论