Flutter框架分析分析系列文章:bash
《Flutter框架分析(一)-- 總覽和Window》app
《Flutter框架分析(三)-- Widget,Element和RenderObject》less
《Flutter框架分析(四)-- Flutter框架的運行》函數
前面幾篇文章介紹了Flutter框架的渲染流水線,window
,初始化以及Widget
,Element
和RenderObject
體系。其中對Widget
,Element
和RenderObject
的介紹主要是一些靜態的說明,瞭解了以上這些技術點以後,在這篇文章裏咱們會經過動態運行的方式來介紹一下Flutter框架是如何運行的。 從以前介紹的渲染流水線能夠知道,這個過程大體能夠分爲兩段操做。第一段是從State.setState()
到去engine那裏請求一幀,第二段就是Vsync信號到來之後渲染流水線開始重建新的一幀最後送入engine去顯示。咱們先來看第一段Flutter框架都作了什麼。動畫
先看一下State.setState()
ui
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
複製代碼
這裏會調用到Element
的markNeedsBuild()
函數。this
void markNeedsBuild() {
if (!_active)
return;
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
複製代碼
Element
首先看本身是否是active的狀態,不是的話就直接返回了,若是是「髒」(dirty)的狀態也直接返回,不是的話會置上這個狀態而後調用BuildOwner
的scheduleBuildFor()
函數,這個BuildOwner
咱們以前介紹過,它的實例是在WidgetsBinding
初始化的時候構建的。每一個Element
的都會持有BuildOwner
的引用。由其父Element
在mount
的時候設置。
void scheduleBuildFor(Element element) {
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
複製代碼
BuildOwner
會維護一個_dirtyElements
列表,全部被標記爲「髒」(dirty)的element
都會被添加進去。在此以前會調用onBuildScheduled()
。這個函數是WidgetsBinding
初始化的時候設置給BuildOwner
的,對應的是WidgetsBinding._handleBuildScheduled()
。
void _handleBuildScheduled() {
ensureVisualUpdate();
}
複製代碼
這裏會調用到ensureVisualUpdate()
。這個函數定義在SchedulerBinding
裏的
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
複製代碼
函數ensureVisualUpdate()
會判斷當前調度所處的狀態,若是是在idle
(空閒)或者postFrameCallbacks
運行狀態則調用scheduleFrame()
。其餘狀態則直接返回。下面這三個狀態正是渲染流水線運行的時候。
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
window.scheduleFrame();
_hasScheduledFrame = true;
}
複製代碼
在函數scheduleFrame()
裏咱們看到了熟悉的window
。這裏就是通知engine去調度一幀的地方了。調度以後會置上_hasScheduledFrame
標誌位,避免重複請求。另一個標誌位_framesEnabled
是表明當前app的狀態,或者說其所處的生命週期是否容許刷新界面。這個狀態有四種:resumed
,inactive
,paused
和suspending
。
resumed
:app可見且能夠響應用戶輸入。inactive
:app不能響應用戶輸入,例如在Android上彈出系統對話框。paused
:app對用戶不可見。suspending
:app掛起??這個狀態貌似Android和iOS都沒有上報。_framesEnabled
只有在resumed
和inactive
狀態下才爲true
。也就是說,只有在這兩個狀態下Flutter框架纔會刷新頁面。
至此第一階段,也就是調度以前的工做作完了。看起來比較簡單,主要就是把須要重建的Element
放入_dirtyElements
列表。接下來Flutter框架會等待Vsync信號到來之後engine回調框架,這就是第二段要作的事情了。
咱們以前說過Vsync信號到來以後,engin會按順序回調window
的兩個回調函數:onBeginFrame()
和onDrawFrame()
。這兩個回調是SchedulerBinding
初始化的時候設置給window
的。對應的是SchedulerBinding.handleBeginFrame()
和SchedulerBinding.handleDrawFrame()
。
這個回調會直接走到SchedulerBinding.handleBeginFrame()
。
void handleBeginFrame(Duration rawTimeStamp) {
...
_hasScheduledFrame = false;
try {
// TRANSIENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
複製代碼
這個函數主要是在依次回調「Transient」回調函數,這些回調函數是在調度以前設置在SchedulerBinding
裏的,這裏的「Transient」意思是臨時的,或者說是一次性的。緣由是這些回調函數只會被調用一次。注意看代碼裏_transientCallbacks
被置爲空Map
了。若是想在下一幀再次調用的話須要提早從新設置回調。這些回調主要和動畫有關係。也就是渲染流水線裏的第一階段,動畫(Animate)階段。關於動畫後續我會再寫文章從框架角度分析一下動畫的機制。
在運行回調以前_schedulerPhase
的狀態被設置爲SchedulerPhase.transientCallbacks
。回調處理完之後狀態更新至SchedulerPhase.midFrameMicrotasks
意思是接下來會處理微任務隊列。處理完微任務之後,engine會接着回調onDrawFrame()
。
這個回調會直接走到SchedulerBinding.handleDrawFrame()
。
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
_currentFrameTimeStamp = null;
}
}
複製代碼
在handleDrawFrame
裏按順序處理了兩類回調,一類叫「Persistent」回調,另外一類叫「Post-Frame」回調。
「Persistent」字面意思是永久的。這類回調一旦註冊之後是不能取消的。主要用來驅動渲染流水線。渲染流水線的構建(build),佈局(layout)和繪製(paint)階段都是在其中一個回調裏的。
「Post-Frame」回調主要是在新幀渲染完成之後的一類調用,此類回調只會被調用一次。
在運行「Persistent」回調以前_schedulerPhase
狀態變爲SchedulerPhase.persistentCallbacks
。在運行「Post-Frame」回調以前_schedulerPhase
狀態變爲SchedulerPhase.postFrameCallbacks
。最終狀態變爲SchedulerPhase.idle
。
這裏咱們主要關注一個「Persistent」回調:WidgetsBinding.drawFrame()
。這個函數是在RendererBinding
初始化的時候加入到「Persistent」回調的。
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
...
}
}
複製代碼
這裏首先會調用buildOwner.buildScope(renderViewElement)
。其入參renderViewElement
是element tree的根節點。此時渲染流水線就進入了構建(build)階段。接下來調用了super.drawFrame()
。這個函數定義在RendererBinding
中。
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
複製代碼
能夠看出渲染流水線的接力棒傳到了pipelineOwner
的手裏,渲染流水線就進入了佈局(layout)階段和繪製(paint)階段。關於最後這兩個階段本篇不作詳細介紹。這裏你們只要知道繪製完成之後Flutter框架最終會調用window.render(scene)
將新幀的數據送入engine顯示到屏幕。
最後調用buildOwner.finalizeTree();
。這個函數的做用是清理再也不須要的Element
節點。在element tree更新之後可能有些節點就再也不須要掛載在樹上了,在finalizeTree()
的時候會將這些節點及其子節點unmount。
void buildScope(Element context, [VoidCallback callback]) {
try {
_scheduledFlushDirtyElements = true;
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
...
}
index += 1;
}
} finally {
for (Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
複製代碼
還記得在調度幀以前會把須要更新的Element
標記爲「髒」(dirty)並放入BuildOwner
的_dirtyElements
列表。這裏Flutter會先按照深度給這個列表排個序。由於Element
在重建的時候其子節點也都會重建,這樣若是父節點和子節點都爲「髒」的話,先重建父節點就避免了子節點的重複重建。
排完序就是遍歷_dirtyElements
列表。依次調用Element.rebuild()
。這個函數又會調用到Element.performRebuild()
。咱們以前介紹Element
的時候說過performRebuild()
由其子類實現。
咱們以前的出發點是State.setState()
。那就先看看StatefulElement
如何作的。它的performRebuild()
是在其父類ComponentElement
裏:
void performRebuild() {
Widget built;
built = build();
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
...
}
}
複製代碼
回憶一下ComponentElement
。這個build()
函數最終會調用到State.build()
了。返回的就是咱們本身實例化的Widget
。拿到這個新Widget
就去調用updateChild()
。以前在講Element
的時候咱們介紹過updateChild()
這個函數。由增,刪,改這麼幾種狀況,對於MyWidget
,從State.setState()
過來是屬於改的狀況。此時會調用child.update(newWidget);
。這個update()
函數又是由各個Element
子類實現的。這裏咱們只列舉幾個比較典型的。
StatefulElement
和StatelessElement
的update()
函數最終都會調用基類Element
的rebuild()
函數。好像在兜圈圈的感受。。。
RenderObjectElement
的update()
函數就比較簡單了
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
複製代碼
更新只是調用了一下RenderObjectWidget.updateRenderObject()
。這個函數咱們以前介紹過,只是把新的配置設置到現有的RenderObject
上。
回到上面那個兜圈圈的問題。理清這裏的調用關係的關鍵就是要搞清楚是此時的Element
是在對本身進行操做仍是對孩子進行操做。假設咱們有這樣的一個三層element tree進行更新重建。
父(StatefulElement)
子(StatefulElement)
孫(LeafRenderObjectElement)
複製代碼
那麼從父節點開始,調用順序以下:
父.rebuild()--->父.performRebuild()--->父.updateChild()--->子.update()--->子.rebuild()--->子.performRebuild()--->子.updateChild()--->孫.update()。
可見構建(build)過程是從須要重建的Element
節點開始一層層向下逐個更新子節點。直到遇到葉子節點爲止。
至此渲染流水線的構建(build)階段就跑完了。接下來就由pipelineOwner
驅動開始佈局(layout)和繪製(paint)階段了。這兩個階段留待之後再給你們介紹一下。
本篇文章從咱們熟悉的State.setState()
函數出發,大體介紹了Flutter框架是如何運行渲染流水線的。整體來講其運行時分爲兩個階段,向engine調度幀以前和Vsync信號到來engine回調Flutter框架以後。剩餘篇幅則是以更新Element
爲例介紹了一下渲染流水線的構建(build)階段都作了一些什麼事情。限於篇幅,沒有更多涉及Element
的新增和刪除步驟。你們感興趣的話能夠直接看源碼來了解相關信息。