呼叫圖
我們用下面的例子來分析Flutter啟動所做的事情
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
複製程式碼
初始化Binding
runApp
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
複製程式碼
在runApp
中,我們會呼叫WidgetsFlutterBinding.ensureInitialized()
來初始化Binding
。
Binding
Binding
是一系列單例,在啟動的時候初始化,其中包括了WidgetsFlutterBinding
、BindingBase
、GestureBinding
、SchedulerBinding
、ServicesBinding
等,下面來分析它們的作用
- BindingBase: 是所有類的基類,負責初始化其它類、初始化一些Native相關的資訊(如平臺是Android還是iOS)、註冊基本的Native事件(如熱更新、退出)
- GestureBinding: 提供
window.onPointerDataPacket
回撥,接受Native事件,負責事件轉換及分發 - SchedulerBinding: 使用了
window.scheduleFrame
來通知Native及使用window.onBeginFrame
和window.onDrawFrame
回撥來接收訊息,主要是負責通知Native在下一偵的事件下發與事件註冊,當我們呼叫setState後,就會觸發此類的方法,等待事件下發後進行渲染 - ServicesBinding: 使用
window.onPlatformMessage
回撥,負責通道相關的初始化及通訊相關的處理 - PaintingBinding: 與繪製相關的函式繫結,還處理一些圖片渲染相關的快取
- SemanticsBinding: 註冊平臺相關的輔助函式
- RendererBinding: 初始化
PipelineOwner
、renderView
、onMetricsChanged
、onTextScaleFactorChanged
、onPlatformBrightnessChanged
、onSemanticsEnabledChanged
onSemanticsAction
等,用於監聽並處理平臺渲染相關如字型、狀態列改變時的事件,是渲染輸與Flutter engine溝通的橋樑 - WidgetsBinding: 初始化
BuildOwner
,註冊window.onLocaleChanged
、onBuildScheduled
等回撥。它是Flutter widget層與engine的橋樑。
渲染樹的構建
attachRootWidget
[-> packages/flutter/lib/src/widgets/binding.dart]
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
複製程式碼
上面會先初始化一個RenderObjectToWidgetAdapter
,它是一個特殊的Widget
,主要用Flutter樹根節點的初始化,其中傳了一個renderView
,上面提到了是在RendererBinding初始化時就已經初始化了,它是根RenderObject
節點,rootWidget
就是我們runApp時傳入的Widget
。
後面呼叫了attachToRenderTree
會開始一系列的樹構建
attachToRenderTree
[-> packages/flutter/lib/src/widgets/binding.dart]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
// 如果element==null執行,第一次渲染element一定為null
if (element == null) {
owner.lockState(() {
// 呼叫createElement建立Element
element = createElement();
assert(element != null);
// 將owner傳給element,讓element持有owner
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
SchedulerBinding.instance!.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
複製程式碼
上面會先根據createElement
建立一個Element
物件,然後傳入owner
,BuildOwner
是Flutter處理更新的類,主要是處理髒節點收集與重繪,需要注意的是,根據Flutter的設計,全域性只有一個BuildOwner
物件,在此處傳入後,它會在子節點傳遞。
然後會呼叫owner.buildScape
,buildScape
主要提供兩個作用,一是為Widget Tree
更新建立一個作用域並執行回撥(像上面這種情況,不過回撥也可以不傳),二是當作用域中存在髒節點,它會呼叫Element.rebuild
方法進行重新構建(就在此時呼叫的Widget的build重新渲染Element)。
接著會呼叫element.mount
,這是一個Element
非常重要的方法,這裡就開始構建三棵樹了.mount
最簡單的作用就是為當前物件繫結父物件及為當前繫結_inheritedWidgets
(你認為的沒錯,就是Flutter中用於資料共享的InheritedWidget
的Element
)。一般我們的子Element
會重寫mount
方法,用於孩子節點的初始化。
mount
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
void _rebuild() {
//...
_child = updateChild(_child, widget.child, _rootChildSlot);
//...
}
複製程式碼
所以在RenderObjectToWidgetElement
中會呼叫_rebuild
然後呼叫updateChild
來初始化孩子節點。
updateChild
[-> packages/flutter/lib/src/widgets/framework.dart]
@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
// ...
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//...
child.update(newWidget);
//...
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
複製程式碼
updateChild
方法用於初始化或者更新孩子節點,當child != null
時,主要是呼叫child.update
來更新child
,否則呼叫inflateWidget
,這裡第一次渲染當然是會呼叫inflateWidget
inflateWidget
[-> packages/flutter/lib/src/widgets/framework.dart]
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
// ...
final Element newChild = newWidget.createElement();
// ...
newChild.mount(this, newSlot);
return newChild;
}
複製程式碼
inflateWidget
方法的作用就是根據Widget
生成Element
同時呼叫child.mount
。這樣,一次樹的迴圈呼叫就接上了。
Widget樹構建
有人問StatelessWidget
和StatefulWidget
的build方法怎麼都沒呼叫。StatelessWidget
、StatefulWidget
對應的Element
是StatelessElement
和StatefulElement
,它們都是繼承至ComponentElement
。根據上面的分析,mount
方法是構建起點,我們可以看下ComponentElement
中的mount
方法。
_firstBuild
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
複製程式碼
呼叫了_firstBuild
方法,然後再呼叫了Element
的rebuild
方法,接著呼叫到ComponentElement
的performRebuild
方法
performRebuild
[-> packages/flutter/lib/src/widgets/framework.dart]
void rebuild() {
performRebuild();
}
//...
@override
void performRebuild() {
Widget? built;
try {
// ...
built = build();
} catch (e, stack) {
// ...
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
// ...
_child = updateChild(null, built, slot);
}
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
複製程式碼
performRebuild
是ComponentElement
實現的父類,其中呼叫了build
方法返回一個Widget
,然後就看到了熟悉的updateChild
方法,這就接上了上面的分析。
RenderObject樹的構建
RenderObjectWidget
RenderObject
是Flutter中用於實際繪製的類,對於StatefulWidget
和StatelessWidget
實際上都是沒有對應的RenderObject
節點的,實際上它們只是提供一個容器,用來組合各種繪製。
只有繼承了RenderObjectWidget
的Widget
才有對應的RenderObject
節點。Flutter中為了方便,又提供了三種繼承至RenderObjectWidget
的類,每個類都有不同作用
- LeafRenderObjectWidget 用於只有渲染功能,無子節點 (如Switch、Radio等)
- SingleChildRenderObjectWidget 只含有一個子節點 (如SizedBox、Padding等)
- MultiChildRenderObjectWidget 含有多個子節點(如Column、Stack等)
上面這些物件只是提供了一個容器給開發者使用,不過不是我們這次分析重點,先看看RenderObjectWidget
[-> packages/flutter/lib/src/widgets/framework.dart]
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
@override
@factory
RenderObjectElement createElement();
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製程式碼
它提供了createRenderObject
方法來構建RenderObject
,看下其對應的Element
->RenderObjectElement
的mount
方法
createRenderObject
[-> packages/flutter/lib/src/widgets/framework.dart]
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
// 將上一步生成的renderObject插入到父renderObject中
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
複製程式碼
其會先呼叫createRenderObject
來構建RenderObject
,然後呼叫attachRenderObject
將上一步生成的renderObject插入到父renderObject中。這裡的通過_findAncestorParentDataElement
來查詢的父類,因為上面說過,不是每個Element
都有對應的RenderObject
節點。
_findAncestorRenderObjectElement
[-> packages/flutter/lib/src/widgets/framework.dart]
RenderObjectElement? _findAncestorRenderObjectElement() {
Element? ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor as RenderObjectElement?;
}
複製程式碼
所以這裡通過父Element
在Element樹
中找上一個為RenderObjectElement
的類,而insertRenderObjectChild
是由上面說的SingleChildRenderObjectWidget
、MultiChildRenderObjectWidget
來具體實現的,主要是將生成的RenderObject
插入到剛剛查詢的父RenderObject
中。因為根Element節點RenderObjectToWidgetElement
是繼承至RenderObjectElement
,所以,只要通過RenderObjectToWidgetElement.renderObject
即可拿到整棵樹RenderObject樹。
總結
上面我們分析了Flutter第一次啟動時的大致過程,WidgetsFlutterBinding
是總工頭,幾乎App所有的事情都是通過它來管理,BuildOwner
是構建樹的負責人,它負責樹的構建和更新,RenderObjectToWidgetAdapter
是一個特殊的Widget
,它是渲染樹的根節點,也是構建樹的起點,Widget
只是負責與開發人員對接,它就是一個工具人,開發人員把資料放到這裡,後面都是通過Element
進行處理並生成RenderObject
(有些Widget不一定會生成RenderObject)。
現在我們只是生成了一個渲染樹,可是介面到底是如何渲染的呢,回到上面的attachToRenderTree
方法,其中構件樹生成後會呼叫SchedulerBinding.instance!.ensureVisualUpdate
方法,這個方法最後會呼叫window.scheduleFrame
,它會傳送通知給Native:我這邊樹都生成好了,你在下一偵給我發個通知,我要開始渲染頁面啦。
想看渲染過程,請聽下回分解。