終於放假啦,不用一直寫業務程式碼了,也終於有點時間可以整理整理筆記啦。
我在這裡先給大家拜個早年,恭祝大家新春快樂,吉祥安康啦!
Flutter系列學習筆記
- Flutter筆記——runApp發生了什麼(原始碼學習)
- Flutter筆記——State.setState發生了什麼(原始碼學習)
- 用Dart寫的身份證號校驗程式碼(可用於flutter專案)
- HelloDart-建構函式特性
- HelloDart-MixIn,土話記錄多繼承機制
- Flutter筆記——MethodChannel(Native&Flutter資料互動)
- Flutter筆記——FlutterActivity
main
FlutterFramework在Flutter層的專案入口是main
函式,預設生成的函式如下
void main() {
runApp(MyApp());
}
複製程式碼
1 runApp
runApp
是一個頂級函式,接受一個Widget
作為rootWidget,這中間發生了什麼嘞?
下文以圖片、原始碼和文字解析的方式輔助學習。由於內容較長,需要分為幾個部分學習,並且序列圖、記錄點和實際執行順序有出入,下文排序主要是為了能夠由淺入深的鋪開runApp過程中的知識點,幫助更好的理解。
2.1 過程一
main
函式,呼叫runApp(Widget)
函式///同main函式,runApp函式也是一個頂級函式 void main() { runApp(MyApp()); } 複製程式碼
- 初始化WidgetsFlutterBinding.instance
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) ..scheduleWarmUpFrame(); } 複製程式碼
ensureInitialized
函式會建立一個WidgetsFlutterBinding
的單例WidgetsBinding.instance
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { ///確認WidgetsBinding.instance是否建立成功,flutter framework工作是是否完成 static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance; } } 複製程式碼
WidgetsFlutterBinding
通過mixIn語法繼承了7個BindingBase
子類,完成整個FlutterFramework層次的系統功能初始化。mixIn語法可參考我的這篇文章HelloDart-MixIn,土話記錄多繼承機制。abstract class BindingBase { ///會按照WidgetsFlutterBinding整合順序,以此呼叫每個WidgetsFlutterBinding子類的 ///initInstances和initServiceExtensions初始化函式。 BindingBase() { developer.Timeline.startSync('Framework initialization'); assert(!_debugInitialized); initInstances(); assert(_debugInitialized); assert(!_debugServiceExtensionsRegistered); initServiceExtensions(); assert(_debugServiceExtensionsRegistered); developer.postEvent('Flutter.FrameworkInitialization', <String, String>{}); developer.Timeline.finishSync(); } } 複製程式碼
initInstances
和initServiceExtensions
該部分比較複雜,這裡先不學習了。簡單介紹一下BindingBase
系列子類的作用,下列內容我也是看註釋的,如有錯誤煩請指出WidgetsFlutterBinding
:將FlutterFramework繫結到FlutterEngine上面GestureBinding
:繫結手勢系統。ServicesBinding
:主要作用與defaultBinaryMessenger
有關,用於和native通訊相關。SchedulerBinding
:改類也繼承了ServicesBinding,主要用於排程幀渲染相關事件。PaintingBinding
:和painting庫繫結SemanticsBinding
:將語義層和FlutterEngine繫結起來。RendererBinding
:將渲染樹與FlutterEngine繫結起來。WidgetsBinding
:將Widget層與FlutterEngine繫結起來。 再次重申一下,上述內容只是看註釋得來的,如果錯漏,感謝指出
初始化完WidgetsFlutterBinding.instance
及一堆系統Binding之後,便開始下一步操作了。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
複製程式碼
這裡使用級聯語法分別呼叫了scheduleAttachRootWidget(Widget)
和scheduleWarmUpFrame
函式,具體過程見下文
2.2 過程二
scheduleAttachRootWidget(app)
:通過級聯的方式,生成了WidgetsFlutterBinding.instance靜態例項並初始化一干系統功能之後,呼叫WidgetsFlutterBinding.scheduleAttachRootWidget(Widget root)
函式。void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) ..scheduleWarmUpFrame(); } 複製程式碼
attachRootWidget(Widget)
:這裡非同步呼叫WidgetsBinding.attachRootWidget(Widget rootWidget)
函式,只為了儘快顯示Flutter專案的UI畫面。void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } 複製程式碼
scheduleWarmUpFrame()
:在上面第二點中,非同步呼叫attachRootWidget
函式,只為了儘快呼叫該函式,去顯示一個幀畫面。這個函式的詳細流程按下不表,等以後再學習。/// Schedule a frame to run as soon as possible, rather than waiting for /// the engine to request a frame in response to a system "Vsync" signal. void scheduleWarmUpFrame() { ... } 複製程式碼
RenderObjectToWidgetAdapter
:接著第2點,attachRootWidget函式作用就是將根Widget、根Element與根RenderObject三個跟物件繫結起來,並將唯一的BuildOwner物件引用作為根物件的持有物件,通過繼承關係層層傳遞。看程式碼///虛擬碼 class WidgetsBinding ...{ void attachRootWidget(Widget rootWidget) { //建立一個RenderObjectToWidgetAdapter物件,泛型T是RenderBox型別, _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]',//renderView[註釋1]是連線到物理裝置輸出層的渲染樹物件 child: rootWidget, //根Widget樹 ).attachToRenderTree(buildOwner, renderViewElement); } } class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { /// Creates a bridge from a [RenderObject] to an [Element] tree. /// /// Used by [WidgetsBinding] to attach the root widget to the [RenderView]. RenderObjectToWidgetAdapter({ this.child, this.container, this.debugShortDescription, }) : super(key: GlobalObjectKey(container)); /// The widget below this widget in the tree. final Widget child; /// The [RenderObject] that is the parent of the [Element] created by this widget. final RenderObjectWithChildMixin<T> container; @override RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this); @override RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container; @override void updateRenderObject(BuildContext context, RenderObject renderObject) { } /// 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); }); // This is most likely the first time the framework is ready to produce // a frame. Ensure that we are asked for one. SchedulerBinding.instance.ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element; } } 複製程式碼
attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ])
:**這裡面關於element
的建立先忽略,在步驟3中學習。**該部分原始碼在第四點中,我們可以看到RenderObjectToWidgetAdapter
會建立一個RenderObjectToWidgetElement
物件,作為WidgetsBinding
中的_renderViewElement
物件。該_renderViewElement
物件也就是整個Element樹中的跟物件,其持有根Widget、根RenderView與BuildOwner物件。
2.3 過程三
過程三主要分析RenderObjectToWidgetAdapter.attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ])
函式,期間還有一些BaseBinding內容摻雜進來,比較複雜。
-
BuildOnwer.lockState(fn)
:///該函式中的_debugStateLocked 布林值,每次呼叫該函式_debugStateLockLevel都會+1, ///callback執行完之後減1,如果在_debugStateLocked等於true期間呼叫setState就會丟擲異常。 void lockState(void callback()) { assert(callback != null); assert(_debugStateLockLevel >= 0); assert(() { _debugStateLockLevel += 1; return true; }()); try { callback(); } finally { assert(() { _debugStateLockLevel -= 1; return true; }()); } assert(_debugStateLockLevel >= 0); } 複製程式碼
-
RenderObjectToWidgetAdapter.createElement()
:會建立一個RenderObjectToWidgetElement
物件。
這裡先不去細看其眾多父類中的屬性和操作 -
element.assignOwner(owner)
:將owner:BuildOnwer
賦值給element
,該owner:BuildOnwer
是整個Element樹的統一管理者。 -
element.mount(null, null)
:上圖忽略了BuildOwner.buildScope(Element context, [ VoidCallback callback ])
函式了,該函式作用在於將一個Element新增進去構建域中,並呼叫VoidCallback
函式作為回撥。
這裡要著重看下element物件一眾父類中的mount函式了
owner.buildScope(element, () { element.mount(null, null); }); class RenderObjectToWidgetElement ...{ @override void mount(Element parent, dynamic newSlot) { assert(parent == null); super.mount(parent, newSlot); //這裡的child暫時為null,忽略rebuild函式 _rebuild(); } } abstract class RootRenderObjectElement extends RenderObjectElement { @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); } } ///RenderObjectElement類中,最重要的屬性,_renderObject ///是該根Element的RenderObject物件,該RenderObject類儲存 ///了Element的位置資訊,Skia是一個2D渲染框架,基於笛卡爾座標軸 ///RenderObject也實現了基本的渲染功能。 abstract class RenderObjectElement extends Element { /// The underlying [RenderObject] for this element. @override RenderObject get renderObject => _renderObject; RenderObject _renderObject; @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); //這個回到步驟二中的RenderObjectToWidgetAdapter類,RendererBinding類初始化之後有個PipelineOwner //物件,PipelineOwner物件中有個rootNode物件,就是該_renderObject了 _renderObject = widget.createRenderObject(this); //這裡newSlot等於null,忽略 attachRenderObject(newSlot); //將dirty置為false, _dirty = false; } } class Element ...{ @mustCallSuper void mount(Element parent, dynamic newSlot) { //一頓賦值 _parent = parent; _slot = newSlot; _depth = _parent != null ? _parent.depth + 1 : 1; _active = true; if (parent != null) // Only assign ownership if the parent is non-null _owner = parent.owner; if (widget.key is GlobalKey) { final GlobalKey key = widget.key; key._register(this); } _updateInheritance(); assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }()); } } 複製程式碼
mount(Element parent, dynamic newSlot)
掛載函式在於將Element掛載到Element樹上去,如果有parent屬性就賦值,掛載之後Element的狀態變為active。 -
SchedulerBinding.instance.ensureVisualUpdate()
:void ensureVisualUpdate() { switch (schedulerPhase) { case SchedulerPhase.idle: case SchedulerPhase.postFrameCallbacks: scheduleFrame(); return; case SchedulerPhase.transientCallbacks: case SchedulerPhase.midFrameMicrotasks: case SchedulerPhase.persistentCallbacks: return; } } 複製程式碼
-
SchedulerBinding.scheduleFrame()
:接著便是呼叫window.scheduleFrame()
函式了,這是一個native函式,純FlutterFramework部分線索就斷了。void scheduleFrame() { if (_hasScheduledFrame || !_framesEnabled) return; assert(() { if (debugPrintScheduleFrameStacks) debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.'); return true; }()); ensureFrameCallbacksRegistered(); window.scheduleFrame(); _hasScheduledFrame = true; } 複製程式碼
3 小結
這裡做個runApp的簡單過程圖,隱藏了許多細節
- FlutterFramework的Dart部分,程式入口是
main
函式。 - 呼叫
runApp(Widget)
函式傳入一個Widget作為根Widget。 Widget
只是一個配置類,不是實際的UI元素。runApp
通過WidgetsFlutterBinding
mixIn繼承一眾父類進行初始化。- 其中,
RendererBinding
父類中的renderView
物件,是實際的渲染物件。 - 通過
RenderObjectToWidgetAdapter
類,生成一個RenderObjectToWidgetElement<RenderBox>
型別的Element作為根Element,並讓Widget、renderView和BuildOwner和根Element產生關係。 - 掛載根Element,呼叫
SchedulerBinding.instance.ensureVisualUpdate()
函式,等待下一幀渲染。 scheduleAttachRootWidget
是一個耗時操作,非同步執行。runApp會優先呼叫scheduleWarmUpFrame()
渲染預熱幀。
上述文章是筆者通過對Flutter1.12版本原始碼進行學習的總結,如有錯漏,還煩請指出糾正,十分感謝。 另外本文只是runApp的原始碼進行了跟蹤學習,Flutter到底是如何渲染到Native裝置、還有BaseBinding系列子類作用等,等之後篇學習筆記哈。