从flutter的入口main方法开始,一步步看下widget是如何被加载的
在Flutter中,一切皆widget,我们有两大widget,statelessWidget和stetefulWidge,会分别看两种下widget是如何被加载出来的,展示的源码会有删减,仅展示跟主题有关的代码
入口到加载
flutter的入口,就是runApp方法,我们也从这个方法开始查看
代码语言:javascript复制void main() {
runApp(const MyApp());
}
进入runApp方法,外部传的widget传给了scheduleAttachRootWidget方法
代码语言:javascript复制void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
其中的..
叫做级联运算符
,表示对第一个对象的操作,而忽略每个方法的返回值,上面的方法,等同于下面的方法
void runApp(Widget app){
final binding = WidgetsFlutterBinding.ensureInitialized()
binding.scheduleAttachRootWidget(app);
binding..scheduleWarmUpFrame();
}
用..
会让写法更简洁,更优雅
继续往下看scheduleAttachRootWidget方法,我会删除一些无关的代码
代码语言:javascript复制 void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
代码语言:javascript复制void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
然后调用到attachToRenderTree
代码语言:javascript复制RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
element = createElement();
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
}
return element!;
}
上面的代码很关键,这里生成的全局第一个element,继续看下createElement()方法
代码语言:javascript复制RenderObjectToWidgetElement<T> createElement() =>
RenderObjectToWidgetElement<T>(this);
这里可以知道,全局的root element是RenderObjectToWidgetElement,这个是系统内部的element,接下来,调用了它的mount方法
代码语言:javascript复制void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_rebuild();
}
代码语言:javascript复制void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
}
}
然后调用了element的updateChild方法,这个方法是一个核心方法,目的是新建或者更新这个element的child element,到这里,我们自己写的传给系统最外层的widget也是在这里被加载的
代码语言:javascript复制Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null) deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
一开始,child是null,newWidget就是我们传的顶层Widget,然后走到inflateWidget方法,这里实际加载element
代码语言:javascript复制Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
先是调用newWidget.createElement()来生成element,然后再调用element的mount方法,这里结合widget源码来看下
代码语言:javascript复制abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
statelessWidget是生成StatelessElement
代码语言:javascript复制abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key? key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin
}
statefulWidget是生成StatefulElement,它们的继承关系是这样
代码语言:javascript复制StatefulElement -> ComponentElement -> Element
StatelessElement -> ComponentElement -> Element
然后再看下它的mount方法
代码语言:javascript复制void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();
}
这里的_firstBuild如果是statefulWidget,会走到它state的两个关键的生命周期调用
代码语言:javascript复制void _firstBuild() {
try {
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
} finally {
}
state.didChangeDependencies();
super._firstBuild();
}
先调用state的initState,所以可以知道,state的第一个调用的生命周期方法就是这个,然后继续调用state的didChangeDependencies方法,然后调用到element的rebuild方法
代码语言:javascript复制void rebuild() {
performRebuild();
}
void performRebuild();
element的performRebuild方法是空实现,最终又回到了ComponentElement来实现
代码语言:javascript复制void performRebuild() {
Widget? built;
try {
built = build();
} catch (e, stack) {
} finally {
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
}
}
这里有两个重要的方法,先是调用了build(),这里就是会最终调用到widget的build方法,就是我们每次实现widget都要实现的方法,然后又调用updateChild方法,继续加载这个widget的子widget,一直循环下去,直到全部加载完
代码语言:javascript复制class StatelessElement extends ComponentElement {
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
}
对于statelessWidget,就直接调用build方法,加载这个widget,再看下statefulWidget
代码语言:javascript复制class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
}
@override
Widget build() => state.build(this);
}
state.build方法,这里就是实现statefulWidget必须实现的build方法
生命周期
通过上面的代码,可以知道statelessWidget的生命周期如下
代码语言:javascript复制1、createElement
2、build
statelessWidget是一旦生成,就不会变,生命周期也比较简单,再看下statefulWidget
代码语言:javascript复制1、createElement
2、createState
3、initState
4、didChangeDependencies
5、build
这里的生命周期是只到加载出来,后续还有更新、销毁等,这里先不提
总结
1、widget的所有方法,都是在同个线程按照从外层到内层逐级往里调用,也就是主线程,dart中叫main isolate 2、如果在widget中,有耗时的方法,应该放在异步执行,可以使用compute,或者isolate提供的异步方法 3、widget的目的,其实是为了生成对应的element,也就是widget树是为了生成对应的element树