Flutter系列學習筆記
- Flutter筆記——幀繪製系列之一(原始碼學習)
- Flutter筆記——runApp發生了什麼(原始碼學習)
- Flutter筆記——State.setState發生了什麼(原始碼學習)
- 用Dart寫的身份證號校驗程式碼(可用於flutter專案)
- HelloDart-建構函式特性
- HelloDart-MixIn,土話記錄多繼承機制
- Flutter筆記——MethodChannel(Native&Flutter資料互動)
- Flutter筆記——FlutterActivity
前言
前兩篇文章Flutter筆記——runApp發生了什麼(原始碼學習)和Flutter筆記——State.setState發生了什麼學習了Flutter中runApp()
、修改UI元素State.setState()
過程。
這篇文章主要學習的是Flutter中實際渲染UI的過程。
1 BaseBinding
BaseBinding
系列是FlutterFramework的核心類,學習Flutter的UI渲染過程會涉及到WidgetsBinding
、RenderBinding
、SchedulerBinding
等。由於Dart的mixIn菱形繼承語法,該部分比較難搞明白,只能從區域性入手,抽絲剝繭般的去學習理解整體流程。
1.1 handleDrawFrame
在我的Flutter筆記——runApp發生了什麼(原始碼學習)文章中,瞭解到WidgetsFlutterBinding.scheduleWarmUpFrame()
函式用於排程展示一個預熱幀。而WidgetsFlutterBinding.scheduleAttachRootWidget(Widget rootWidget)
函式使用Timer包裹,作為一個非同步執行函式,在它執行完畢之時最終會呼叫WidgetsBinding.handleDrawFrame()
函式繪製幀。
那麼handleDrawFrame()
函式到底發生了什麼?
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
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;
Timeline.finishSync(); // end the Frame
assert(() {
if (debugPrintEndFrameBanner)
debugPrint('▀' * _debugBanner.length);
_debugBanner = null;
return true;
}());
_currentFrameTimeStamp = null;
}
}
}
複製程式碼
首先學習WidgetsBinding
類,見註釋
Scheduler for running the following:
- Transient callbacks, triggered by the system's [Window.onBeginFrame] callback, for synchronizing the application's behavior to the system's display. For example, [Ticker]s and [AnimationController]s trigger from these.
- Persistent callbacks, triggered by the system's [Window.onDrawFrame] callback, for updating the system's display after transient callbacks have executed. For example, the rendering layer uses this to drive its rendering pipeline.
- Post-frame callbacks, which are run after persistent callbacks, just before returning from the [Window.onDrawFrame] callback.
- Non-rendering tasks, to be run between frames. These are given a priority and are executed in priority order according to a [schedulingStrategy]
簡單理解下,該類主要作用就是排程幀渲染任務,當然也可以執行非渲染任務。主要是瞬間渲染、持久渲染與渲染回撥任務等,例如持久的幀渲染監聽註冊WidgetsBinding.instance.addPersistentFrameCallback(callback)
就是該類的作用了。
回到handleDrawFrame()
函式,這裡面迴圈執行SchedulerBinding._persistentCallbacks
與SchedulerBinding._postFrameCallbacks
的註冊回撥之外,好像沒做其他事情哦?那麼線索斷了嗎?
1.2 initInstances
這裡吐槽下mixIn菱形繼承,這個語法特性真的香嗎?
這裡把眼光回到BaseBinding
系列的初始化函式中,我們可以在RendererBinding.initInstances()
函式中,找到SchedulerBinding.addPersistentFrameCallback(FrameCallback callback)
函式的呼叫,這意味著在RendererBinding.initInstances()
初始化階段,已經註冊了一個關鍵函式,噔噔瞪,見下面原始碼
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
//重點
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
}
//重點
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
}
複製程式碼
我們可以看到,在SchedulerBinding._persistentCallbacks
已經註冊了drawFrame
函式回撥,到了這裡handleDrawFrame
渲染幀的線索又接上了,接著往下看。
1.3 drawFrame
drawFrame()
函式有2處實現(有一處Test環境,忽略),並且都被WidgetsFlutterBinding
繼承,這個mixIn真的香嗎?
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
void drawFrame() {
assert(renderView != null);
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.
}
}
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void drawFrame() {
assert(!debugBuildingDirtyElements);
assert(() {
debugBuildingDirtyElements = true;
return true;
}());
if (_needToReportFirstFrame && _reportFirstFrame) {
assert(!_firstFrameCompleter.isCompleted);
TimingsCallback firstFrameCallback;
firstFrameCallback = (List<FrameTiming> timings) {
if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
_firstFrameCompleter.complete();
};
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
}
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
if (!kReleaseMode) {
if (_needToReportFirstFrame && _reportFirstFrame) {
developer.Timeline.instantSync('Widgets built first useful frame');
}
}
_needToReportFirstFrame = false;
}
}
複製程式碼
在1.2中,我們知道drawFrame
在每個handleDrawFrame
函式中都會被呼叫,我們的WidgetsFlutterBinding
繼承自RendererBinding
和WidgetsBinding
,見下圖的順序看看drawFrame
到底發生了什麼,再進行原始碼追蹤
過程比較複雜,原始碼學習按照序列圖中的順序來
WidgetsBinding.drawFrame()
:該函式在每一次handleDrawFrame都會被呼叫,並且還會呼叫super.drawFrame
函式///虛擬碼 mixin WidgetsBinding ...{ ///忽略斷言和除錯部分程式碼 @override void drawFrame() { try { ///如果renderViewElement不為空,呼叫BuildOwner.buildScope函式,生成WidgetTree更新域 if (renderViewElement != null){ buildOwner.buildScope(renderViewElement); } //呼叫RenderBinding.drawFrame函式 super.drawFrame(); // buildOwner.finalizeTree(); } finally { assert(() { debugBuildingDirtyElements = false; return true; }()); } if (!kReleaseMode) { if (_needToReportFirstFrame && _reportFirstFrame) { developer.Timeline.instantSync('Widgets built first useful frame'); } } _needToReportFirstFrame = false; } } 複製程式碼
buildOwner.buildScope(renderViewElement)
:這裡的renderViewElement
是一個RenderObjectToWidgetElement<RenderBox>
物件,在runApp(Widget app)
函式中被初始化,不瞭解的請看我的這篇文章Flutter筆記——runApp發生了什麼(原始碼學習)。
buildOwner.buildScope(renderViewElement)
函式的作用是建立WidgetTree構建的域。///刪除斷言和callback相關程式碼 void buildScope(Element context, [ VoidCallback callback ]) { Timeline.startSync('Build', arguments: timelineWhitelistArguments); try{ _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; int dirtyCount = _dirtyElements.length; int index = 0; while (index < dirtyCount) { try { _dirtyElements[index].rebuild(); } catch (e, stack) { _debugReportException( ErrorDescription('while rebuilding dirty elements'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index])); yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount'); }, ); } index += 1; if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) { _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; dirtyCount = _dirtyElements.length; while (index > 0 && _dirtyElements[index - 1].dirty) { index -= 1; } } } } finally { for (Element element in _dirtyElements) { element._inDirtyList = false; } _dirtyElements.clear(); _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; Timeline.finishSync(); } } 複製程式碼
_dirtyElements.sort(Element._sort)
:排列Element,根據Element
中的depth
值,depth
值是當期Element
所在樹的層次整數。每個Element
的depth
值都大於ParentElement
的depth
值static int _sort(Element a, Element b) { if (a.depth < b.depth) return -1; if (b.depth < a.depth) return 1; if (b.dirty && !a.dirty) return -1; if (a.dirty && !b.dirty) return 1; return 0; } 複製程式碼
_dirtyElements[index].rebuild()
:遍歷_dirtyElements容器中的元素,呼叫它們的rebuild()
函式。element.rebuild()
:這裡以ComponentElement
作為示例,rebuild()
函式原始碼如下void rebuild() { ///刪除很多斷言和其他程式碼 performRebuild(); } 複製程式碼
ComponentElement.performRebuild()
:在這裡我們可以看到performRebuild()
函式會呼叫Element中的build()
函式,這對於我們應該是最熟悉的Flutter程式碼之一了。這裡面的built = build()
有幾個繼承,StatefulWidget
通過createState()
函式生成State
,再通過State
的build():Widget
函式生成Widget。@override void performRebuild() { ///刪除很多斷言和其他程式碼 Widget built; try { built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); }, ), ); } finally { _dirty = false; } try { _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); }, ), ); _child = updateChild(null, built, slot); } } 複製程式碼
updateChild(Element child, Widget newWidget, dynamic newSlot)
:更新Element
中的Widget
物件,這裡面有三個引數,第一個是之前的Widget
物件,也就是類物件child
。第二個是新生成的newWidget
物件,由build()
函式生成,第三個newSlot
是父Element給與子Element的位置引數,如果slot位置發生了變化,即使child
與newWidget
相同,也會重新渲染。@protected Element updateChild(Element child, Widget newWidget, dynamic newSlot) { if (newWidget == null) { if (child != null) deactivateChild(child); return null; } if (child != null) { if (child.widget == newWidget) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); return child; } if (Widget.canUpdate(child.widget, newWidget)) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); assert(child.widget == newWidget); assert(() { child.owner._debugElementWasRebuilt(child); return true; }()); return child; } deactivateChild(child); assert(child._parent == null); } return inflateWidget(newWidget, newSlot); } 複製程式碼
Element inflateWidget(Widget newWidget, dynamic newSlot)
:根據給定的Widget
和newSlot
生成一個Element,該方法通常由updateChild()
函式直接呼叫。如果該Widget
生成Element
已經存在或者存在相同的GlobalKey
將會複用。該函式還會呼叫Widget.canUpdate(Widget oldWidget, Widget newWidget)
來比較Widget物件是否相同。 該部分原始碼較長,在之後文章看是否記錄學習,這裡知道其作用即可。- 如果newWidget的key是
GlobalKey
,並且通過Element _retakeInactiveElement(GlobalKey key, Widget newWidget)
能拿回來一個Element,那麼在更新狀態與slot、配置之後便返回一個Element
。 - 不能從key中拿回已有的Element,會呼叫
Element newChild = newWidget.createElement()
生成一個新的newChild
,並掛載它newChild.mount(this, newSlot)
並返回。
- 如果newWidget的key是
super.drawFrame()
:也就是RenderBinding.drawFrame()
函式,該函式涉及知識點較多,下篇文章學習。它主要涉及到了RenderObject
、Rect
、PipelineOwner
等知識點。buildOwner.finalizeTree()
:呼叫該函式來完成元素構建。
2 小結
- 本篇文章從預熱幀
WidgetsFlutterBinding.scheduleWarmUpFrame()
函式入手,找到FlutterFramework渲染幀的過程函式handleDrawFrame()
,再通過BaseBinding
系列找到drawFrame()
的持久監聽與回撥來學習幀繪製的部分內容。 - 本文從
Element
的create
與update
中,也找到了State.setState
時,有些UI元素沒有重繪的根本原因,也瞭解了key的作用。 BaseBinding
中的WidgetsBinding
、RenderBinding
、SchedulerBinding
等子類是FlutterFramework幀渲染的核心類。本文從drawFrame入手學習了部分內容,另外BuildOwner
全域性管理類也要著重瞭解。- 本文篇章有限,還有許多內容沒有學習到,等下篇文章再著重學習
RenderBinding.drawFrame()
的作用,之後再做一個階段性總結。
謝謝閱讀,如有錯誤勞煩指出糾正,十分感謝,新春快樂哦!