關注個人簡介入撒,面試不迷路~
main入口啟動
- Flutter的主入口在"lib/main.dart"的
main()
函數(shù)中轻黑。在Flutter應用中藐窄,main()
函數(shù)最簡單的實現(xiàn)如下:
void main() {
runApp(MyApp());
}
- 可以看到
main()
函數(shù)只調(diào)用了一個runApp()
方法遮精,runApp()
方法中都做了什么:
void runApp(Widget app) {
//初始化操作
WidgetsFlutterBinding.ensureInitialized()
//頁面渲染
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
* 參數(shù)`app`是一個Widget移迫,是Flutter應用啟動后要展示的第一個Widget旺嬉。
* `WidgetsFlutterBinding`正是綁定widget 框架和Flutter engine的橋梁。
-
ensureInitialized()
方法
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;
// 繪制回調(diào)
VoidCallback get onDrawFrame => _onDrawFrame;
// 發(fā)送平臺消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
... //其它屬性及回調(diào)
}
* `Window`類包含了當前設備和系統(tǒng)的一些信息以及Flutter Engine的一些回調(diào)“Φ兀現(xiàn)在回來看看`WidgetsFlutterBinding`混入的各種Binding据悔。通過查看這些 Binding的源碼,可以發(fā)現(xiàn)這些Binding中基本都是監(jiān)聽并處理`Window`對象的一些事件耘沼,然后將這些事件按照Framework的模型包裝极颓、抽象然后分發(fā)∪亨停可以看到`WidgetsFlutterBinding`正是粘連Flutter engine與上層Framework的“膠水”菠隆。
- 看看
attachToRenderTree
的源碼實現(xiàn):
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
if (element == null) {
...// 代碼處理
} else {
...// 代碼處理
}
return element;
}
- 該方法負責創(chuàng)建根element,即
RenderObjectToWidgetElement
,并且將element與widget 進行關聯(lián)骇径,即創(chuàng)建出 widget樹對應的element樹躯肌。- 如果element 已經(jīng)創(chuàng)建過了,則將根element 中關聯(lián)的widget 設為新的既峡,由此可以看出element 只會創(chuàng)建一次羡榴,后面會進行復用。那么
BuildOwner
是什么呢运敢?其實他就是widget framework的管理類校仑,它跟蹤哪些widget需要重新構建。
- 如果element 已經(jīng)創(chuàng)建過了,則將根element 中關聯(lián)的widget 設為新的既峡,由此可以看出element 只會創(chuàng)建一次羡榴,后面會進行復用。那么
頁面渲染
- 回到
runApp
的實現(xiàn)中传惠,當調(diào)用完attachRootWidget
后迄沫,最后一行會調(diào)用WidgetsFlutterBinding
實例的scheduleWarmUpFrame()
方法,該方法的實現(xiàn)在SchedulerBinding
中卦方,它被調(diào)用后會立即進行一次繪制(而不是等待"vsync"信號)羊瘩,在此次繪制結(jié)束前,該方法會鎖定事件分發(fā)盼砍,也就是說在本次繪制結(jié)束完成之前Flutter將不會響應各種事件尘吗,這可以保證在繪制過程中不會再觸發(fā)新的重繪。 - 下面是
scheduleWarmUpFrame()
方法的部分實現(xiàn)(省略了無關代碼):
void scheduleWarmUpFrame() {
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
});
// 鎖定事件
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
...
}
* 可以看到該方法中主要調(diào)用了`handleBeginFrame()` 和 `handleDrawFrame()` 兩個方法浇坐,在看這兩個方法之前我們首先了解一下Frame 和 FrameCallback 的概念:
* Frame: 一次繪制過程睬捶,我們稱其為一幀。Flutter engine受顯示器垂直同步信號"VSync"的驅(qū)使不斷的觸發(fā)繪制近刘。我們之前說的Flutter可以實現(xiàn)60fps(Frame Per-Second)擒贸,就是指一秒鐘可以觸發(fā)60次重繪,F(xiàn)PS值越大觉渴,界面就越流暢介劫。
* FrameCallback:`SchedulerBinding` 類中有三個FrameCallback回調(diào)隊列, 在一次繪制過程中案淋,這三個回調(diào)隊列會放在不同時機被執(zhí)行:
* 1. `transientCallbacks`:用于存放一些臨時回調(diào)座韵,一般存放動畫回調(diào)√呔可以通過`SchedulerBinding.instance.scheduleFrameCallback` 添加回調(diào)回右。
* 2. `persistentCallbacks`:用于存放一些持久的回調(diào),不能在此類回調(diào)中再請求新的繪制幀漱挚,持久回調(diào)一經(jīng)注冊則不能移除。`SchedulerBinding.instance.addPersitentFrameCallback()`渺氧,這個回調(diào)中處理了布局與繪制工作旨涝。
* 3. `postFrameCallbacks`:在Frame結(jié)束時只會被調(diào)用一次,調(diào)用后會被系統(tǒng)移除,可由 `SchedulerBinding.instance.addPostFrameCallback()` 注冊白华,注意慨默,不要在此類回調(diào)中再觸發(fā)新的Frame,這可以會導致循環(huá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`隊列添加了一個回調(diào) `_handlePersistentFrameCallback`:
```
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
```
- 該方法直接調(diào)用了
RendererBinding
的drawFrame()
方法
flushLayout()
- 代碼如下所示
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()
方法中澡为,該方法中會調(diào)用performLayout()
進行重新布局漂坏。
flushCompositingBits()
- 代碼如下所示
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
則需要重繪顶别。
flushPaint()
- 代碼如下所示
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()`來繪制的,該方法最終會調(diào)用Flutter engine提供的Canvas API來完成繪制图呢。
compositeFrame()
- 代碼如下所示
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();
}
}
- 這個方法中有一個
Scene
對象条篷,Scene對象是一個數(shù)據(jù)結(jié)構,保存最終渲染后的像素信息蛤织。- 這個方法將Canvas畫好的
Scene
傳給window.render()
方法赴叹,該方法會直接將scene信息發(fā)送給Flutter engine,最終由engine將圖像畫在設備屏幕上指蚜。
- 這個方法將Canvas畫好的
最后
- 需要注意:由于
RendererBinding
只是一個mixin乞巧,而with它的是WidgetsBinding
,所以需要看看WidgetsBinding
中是否重寫該方法摊鸡,查看WidgetsBinding
的drawFrame()
方法源碼:
@override
void drawFrame() {
...//省略無關代碼
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //調(diào)用RendererBinding的drawFrame()方法
buildOwner.finalizeTree();
}
}
- 發(fā)現(xiàn)在調(diào)用
RendererBinding.drawFrame()
方法前會調(diào)用buildOwner.buildScope()
(非首次繪制)绽媒,該方法會將被標記為“dirty” 的 element 進行rebuild()
。