本節(jié)我們主要介紹一下 Flutter 從啟動(dòng)到顯示的過程。
Flutter 的入口在"lib/main.dart"的main()
函數(shù)中,它是 Dart 應(yīng)用程序的起點(diǎn)。在 Flutter 應(yīng)用中,main()
函數(shù)最簡(jiǎn)單的實(shí)現(xiàn)如下:
void main() {
runApp(MyApp());
}
可以看main()
函數(shù)只調(diào)用了一個(gè)runApp()
方法,我們看看runApp()
方法中都做了什么:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
參數(shù)app
是一個(gè) widget,它是 Flutter 應(yīng)用啟動(dòng)后要展示的第一個(gè) Widget。而WidgetsFlutterBinding
正是綁定 widget 框架和 Flutter engine 的橋梁,定義如下:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
可以看到WidgetsFlutterBinding
繼承自BindingBase
并混入了很多Binding
,在介紹這些Binding
之前我們先介紹一下Window
,下面是Window
的官方解釋:
The most basic interface to the host operating system's user interface.
很明顯,Window
正是 Flutter Framework 連接宿主操作系統(tǒng)的接口。我們看一下Window
類的部分定義:
class Window {
// 當(dāng)前設(shè)備的DPI,即一個(gè)邏輯像素顯示多少物理像素,數(shù)字越大,顯示效果就越精細(xì)保真。
// DPI是設(shè)備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5
double get devicePixelRatio => _devicePixelRatio;
// Flutter UI繪制區(qū)域的大小
Size get physicalSize => _physicalSize;
// 當(dāng)前系統(tǒng)默認(rèn)的語言Locale
Locale get locale;
// 當(dāng)前系統(tǒng)字體縮放比例。
double get textScaleFactor => _textScaleFactor;
// 當(dāng)繪制區(qū)域大小改變回調(diào)
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale發(fā)生變化回調(diào)
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系統(tǒng)字體縮放變化回調(diào)
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 繪制前回調(diào),一般會(huì)受顯示器的垂直同步信號(hào)VSync驅(qū)動(dòng),當(dāng)屏幕刷新時(shí)就會(huì)被調(diào)用
FrameCallback get onBeginFrame => _onBeginFrame;
// 繪制回調(diào)
VoidCallback get onDrawFrame => _onDrawFrame;
// 點(diǎn)擊或指針事件回調(diào)
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 調(diào)度Frame,該方法執(zhí)行后,onBeginFrame和onDrawFrame將緊接著會(huì)在合適時(shí)機(jī)被調(diào)用,
// 此方法會(huì)直接調(diào)用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新應(yīng)用在GPU上的渲染,此方法會(huì)直接調(diào)用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 發(fā)送平臺(tái)消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平臺(tái)通道消息處理回調(diào)
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它屬性及回調(diào)
}
可以看到Window
類包含了當(dāng)前設(shè)備和系統(tǒng)的一些信息以及 Flutter Engine 的一些回調(diào)。現(xiàn)在我們?cè)倩貋砜纯?code>WidgetsFlutterBinding混入的各種 Binding。通過查看這些 Binding 的源碼,我們可以發(fā)現(xiàn)這些 Binding 中基本都是監(jiān)聽并處理Window
對(duì)象的一些事件,然后將這些事件按照 Framework 的模型包裝、抽象然后分發(fā)??梢钥吹?code>WidgetsFlutterBinding正是粘連 Flutter engine 與上層 Framework 的“膠水”。
GestureBinding
:提供了window.onPointerDataPacket
回調(diào),綁定 Framework 手勢(shì)子系統(tǒng),是 Framework 事件模型與底層事件的綁定入口。ServicesBinding
:提供了window.onPlatformMessage
回調(diào), 用于綁定平臺(tái)消息通道(message channel),主要處理原生和 Flutter 通信。SchedulerBinding
:提供了window.onBeginFrame
和window.onDrawFrame
回調(diào),監(jiān)聽刷新事件,綁定 Framework 繪制調(diào)度子系統(tǒng)。PaintingBinding
:綁定繪制庫,主要用于處理圖片緩存。SemanticsBinding
:語義化層與 Flutter engine 的橋梁,主要是輔助功能的底層支持。RendererBinding
: 提供了window.onMetricsChanged
、window.onTextScaleFactorChanged
等回調(diào)。它是渲染樹與 Flutter engine 的橋梁。WidgetsBinding
:提供了window.onLocaleChanged
、onBuildScheduled
等回調(diào)。它是Flutter widget層與engine的橋梁。
WidgetsFlutterBinding.ensureInitialized()
負(fù)責(zé)初始化一個(gè)WidgetsBinding
的全局單例,緊接著會(huì)調(diào)用WidgetsBinding
的attachRootWidget
方法,該方法負(fù)責(zé)將根 Widget 添加到RenderView
上,代碼如下:
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
注意,代碼中的有renderView
和renderViewElement
兩個(gè)變量,renderView
是一個(gè)RenderObject
,它是渲染樹的根,而renderViewElement
是renderView
對(duì)應(yīng)的Element
對(duì)象,可見該方法主要完成了根 widget 到根 RenderObject
再到根Element
的整個(gè)關(guān)聯(lián)過程。我們看看attachToRenderTree
的源碼實(shí)現(xiàn):
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
該方法負(fù)責(zé)創(chuàng)建根 element,即RenderObjectToWidgetElement
,并且將 element 與 widget 進(jìn)行關(guān)聯(lián),即創(chuàng)建出 widget 樹對(duì)應(yīng)的 element 樹。如果 element 已經(jīng)創(chuàng)建過了,則將根 element 中關(guān)聯(lián)的 widget 設(shè)為新的,由此可以看出 element 只會(huì)創(chuàng)建一次,后面會(huì)進(jìn)行復(fù)用。那么BuildOwner
是什么呢?其實(shí)他就是 widget framework 的管理類,它跟蹤哪些 widget 需要重新構(gòu)建。
回到runApp
的實(shí)現(xiàn)中,當(dāng)調(diào)用完attachRootWidget
后,最后一行會(huì)調(diào)用 WidgetsFlutterBinding
實(shí)例的 scheduleWarmUpFrame()
方法,該方法的實(shí)現(xiàn)在SchedulerBinding
中,它被調(diào)用后會(huì)立即進(jìn)行一次繪制(而不是等待"vsync" 信號(hào)),在此次繪制結(jié)束前,該方法會(huì)鎖定事件分發(fā),也就是說在本次繪制結(jié)束完成之前 Flutter 將不會(huì)響應(yīng)各種事件,這可以保證在繪制過程中不會(huì)再觸發(fā)新的重繪。下面是scheduleWarmUpFrame()
方法的部分實(shí)現(xiàn)(省略了無關(guān)代碼):
void scheduleWarmUpFrame() {
...
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
});
// 鎖定事件
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
...
}
可以看到該方法中主要調(diào)用了handleBeginFrame()
和 handleDrawFrame()
兩個(gè)方法,在看這兩個(gè)方法之前我們首先了解一下Frame 和 FrameCallback 的概念:
SchedulerBinding
類中有三個(gè) FrameCallback 回調(diào)隊(duì)列, 在一次繪制過程中,這三個(gè)回調(diào)隊(duì)列會(huì)放在不同時(shí)機(jī)被執(zhí)行:
transientCallbacks
:用于存放一些臨時(shí)回調(diào),一般存放動(dòng)畫回調(diào)??梢酝ㄟ^SchedulerBinding.instance.scheduleFrameCallback
添加回調(diào)。persistentCallbacks
:用于存放一些持久的回調(diào),不能在此類回調(diào)中再請(qǐng)求新的繪制幀,持久回調(diào)一經(jīng)注冊(cè)則不能移除。SchedulerBinding.instance.addPersitentFrameCallback()
,這個(gè)回調(diào)中處理了布局與繪制工作。postFrameCallbacks
:在 Frame 結(jié)束時(shí)只會(huì)被調(diào)用一次,調(diào)用后會(huì)被系統(tǒng)移除,可由 SchedulerBinding.instance.addPostFrameCallback()
注冊(cè),注意,不要在此類回調(diào)中再觸發(fā)新的 Frame,這可以會(huì)導(dǎo)致循環(huán)刷新。
現(xiàn)在請(qǐng)讀者自行查看handleBeginFrame()
和 handleDrawFrame()
兩個(gè)方法的源碼,可以發(fā)現(xiàn)前者主要是執(zhí)行了transientCallbacks
隊(duì)列,而后者執(zhí)行了 persistentCallbacks
和 postFrameCallbacks
隊(duì)列。
渲染和繪制邏輯在RendererBinding
中實(shí)現(xiàn),查看其源碼,發(fā)現(xiàn)在其initInstances()
方法中有如下代碼:
void initInstances() {
... //省略無關(guān)代碼
//監(jiān)聽Window對(duì)象的事件
ui.window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
//添加PersistentFrameCallback
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
我們看最后一行,通過addPersistentFrameCallback
向persistentCallbacks
隊(duì)列添加了一個(gè)回調(diào) _handlePersistentFrameCallback
:
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
該方法直接調(diào)用了RendererBinding
的drawFrame()
方法:
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout(); //布局
pipelineOwner.flushCompositingBits(); //重繪之前的預(yù)處理操作,檢查RenderObject是否需要重繪
pipelineOwner.flushPaint(); // 重繪
renderView.compositeFrame(); // 將需要繪制的比特?cái)?shù)據(jù)發(fā)給GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
我們看看這些方法分別做了什么:
void flushLayout() {
...
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in
dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
}
}
源碼很簡(jiǎn)單,該方法主要任務(wù)是更新了所有被標(biāo)記為“dirty”的RenderObject
的布局信息。主要的動(dòng)作發(fā)生在node._layoutWithoutResize()
方法中,該方法中會(huì)調(diào)用performLayout()
進(jìn)行重新布局。
void flushCompositingBits() {
_nodesNeedingCompositingBitsUpdate.sort(
(RenderObject a, RenderObject b) => a.depth - b.depth
);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits(); //更新RenderObject.needsCompositing屬性值
}
_nodesNeedingCompositingBitsUpdate.clear();
}
檢查RenderObject
是否需要重繪,然后更新RenderObject.needsCompositing
屬性,如果該屬性值被標(biāo)記為true
則需要重繪。
void flushPaint() {
...
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 反向遍歷需要重繪的RenderObject
for (RenderObject node in
dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
// 真正的繪制邏輯
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
}
}
該方法進(jìn)行了最終的繪制,可以看出它不是重繪了所有 RenderObject
,而是只重繪了需要重繪的 RenderObject
。真正的繪制是通過PaintingContext.repaintCompositedChild()
來繪制的,該方法最終會(huì)調(diào)用 Flutter engine 提供的 Canvas API 來完成繪制。
void compositeFrame() {
...
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene); //調(diào)用Flutter engine的渲染API
scene.dispose();
} finally {
Timeline.finishSync();
}
}
這個(gè)方法中有一個(gè)Scene
對(duì)象,Scene 對(duì)象是一個(gè)數(shù)據(jù)結(jié)構(gòu),保存最終渲染后的像素信息。這個(gè)方法將 Canvas 畫好的Scene
傳給window.render()
方法,該方法會(huì)直接將 scene 信息發(fā)送給 Flutter engine,最終由 engine 將圖像畫在設(shè)備屏幕上。
需要注意的是:由于RendererBinding
只是一個(gè) mixin,而 with 它的是WidgetsBinding
,所以我們需要看看WidgetsBinding
中是否重寫該方法,查看WidgetsBinding
的drawFrame()
方法源碼:
@override
void drawFrame() {
...//省略無關(guān)代碼
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //調(diào)用RendererBinding的drawFrame()方法
buildOwner.finalizeTree();
}
}
我們發(fā)現(xiàn)在調(diào)用RendererBinding.drawFrame()
方法前會(huì)調(diào)用 buildOwner.buildScope()
(非首次繪制),該方法會(huì)將被標(biāo)記為“dirty” 的 element 進(jìn)行 rebuild()
。
本節(jié)介紹了 Flutter APP 從啟動(dòng)到顯示到屏幕上的主流程,讀者可以結(jié)合前面章節(jié)對(duì) Widget、Element 以及 RenderObject 的介紹來加強(qiáng)細(xì)節(jié)理解。
更多建議: