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