mixin機制並不是dart首創,在其餘前端語言中也有很普遍應用。但對於一個剛開始看Flutter源碼的客戶端開發來講,各類mixin直接勸退,不得不先惡補下mixin。
mixin首要特性就是實現函數複用,因此在開始mixin機制解析前,先從第一個問題出發:前端
對應面嚮對象語言來講,一般的作法就是繼承,即在基類中實現某個函數,子類繼承該基類就可以使用函數了。舉個例子:狗和鷹均可以移動,經過在基類Animal中實現moveTo方法,Dog和Eagle繼承後都能使用moveTo方法了java
class Animal { void moveTo(){...//Do some thing} } class Dog extends Animal{} class Eagle extends Animal{} 複製代碼
但經過繼承的方式實現函數複用會有另一個問題。即函數是與基類耦合的,子類繼承了基類後就繼承了基類的全部方法和屬性。若是汽車要複用moveTo方法的話顯然繼承Animal是不合適的,由此引伸出問題2:git
首先確定要把moveTo方法從Animal中解耦出來,定義一個接口CommonBehavior來實現。在java8及Kotlin的接口支持函數的默認實現,java8須要default關鍵字。kotlin接口定義的方法一樣支持默認實現,不過爲了兼容java以前的版本,採用的是編譯時生成一個靜態類,經過調用靜態類的靜態方法moveTo方法來實現。github
interface CommonBehavior { defalut void moveTo(){...//Do some Thing} } class Dog implements CommonBehavior{} 複製代碼
此外Koltin還能夠經過類委託來實現方法複用。除了接口外,還需聲明一個moveTo具體實現的委託類BehaviorDelegate。緩存
interface CommonBehavior { fun moveTo() } class BehaviorDelegate : CommonBehavior { override fun moveTo(){...//Do some thing} } class Dog : CommonBehavior by BehaviorDelegate() 複製代碼
Kotlin類委託機制就再也不詳述了,原理是經過代理實現。Java固然也是能夠經過代理實現的,不過沒有by這種語法糖用起來爽。轉到正題:markdown
(PS:kotlin類委託配合動態代理能夠實現接口的選擇性實現,有興趣能夠看看讀源碼-LeakCanary2.4解析中2.2章節。)app
Dart中沒有interface關鍵字,而是用mixin進行混合,將moveTo抽離到一個mixin修飾的CommonBehavior。這樣就能經過混入CommonBehavior直接使用moveTo方法了。async
class Animal{} mixin CommonBehavior{ moveTo(){...//Do some thing}; } class Dog extends Animal with CommonBehavior {} 複製代碼
實現代碼複用只是mixin的基本功能,mixin還有其餘強大的特性。
混入多個mixin時會向前覆蓋,即後混入的mixin類中的方法會覆蓋前面繼承或混入的相同方法。咱們先來看一個簡單的例1ide
//例1 class SuperClass{ fun()=>print('SuperClass'); } mixin MixA{ fun()=>print('MixA'); } mixin MixB{ fun()=>print('MixB'); } class Child extends SuperClass with MixA,MixB {} main(){ Child child = Child(); child.fun(); } 複製代碼
運行後的結果:
MixB
先混入的MixA含有fun(),覆蓋了SuperClass的fun()。然後混入的MixB也有fun(),覆蓋了MixA的方法,最終調用的是MixB的fun()方法。由此也能夠知道後混入的mixin類的方法是最早調用的。爲了驗證這一調用順序咱們對例1進行以下改動:
函數
//例2 class SuperClass{ fun(){ print('-->SuperClass.fun()'); print('-->SuperClass'); } } mixin MixA on SuperClass{ fun(){ print('-->MixA.fun()'); super.fun(); print('-->MixA'); } } mixin MixB on SuperClass{ fun(){ print('-->MixB.fun()'); super.fun(); print('-->MixB'); } } class Child extends SuperClass with MixA,MixB {} main(){ Child child = Child(); child.fun(); } 複製代碼
輸出結果:
-->MixB.fun() -->MixA.fun() -->SuperClass.fun() -->SuperClass -->MixA -->MixB 複製代碼
由輸出結果能夠看出經過mixin機制的調用關係,在形式上實現了相似"多繼承"同樣的繼承鏈。
這裏使用了mixin on。mixin MixA on SuperClass 這樣支持在MixA中像繼承同樣經過super來調用SuperClass的方法。同時也限定了要混入MixA的類必須繼承自SuperClass。
在繼承關係方面,輸出結果給人一種child-->MixB-->MixA-->SuperClass繼承關係的錯覺,其實否則。混合機制至關於在SuperClass的頂層混入mixin類並生成一個新類,相似於Android中的幀佈局SuperClass屬於最下層父佈局,mixin類屬於其中的子元素,mixin類之間並沒有父子關係相互解耦。後加入的mixin類在「幀佈局」中層級越靠上,會覆蓋下層的相同位置方法。用僞代碼來描述上面例子中的繼承關係:
class SuperMixA = SuperClass with MixA; class SuperMixAMixB = SuperMixA with MixB; class Child extends SuperMixAMixB {} 複製代碼
這種"繼承鏈"以下圖所示,Child最終繼承的是在Super、MixA、MixB的一個混合,Child 的實例child 類型 屬於Super、MixA、MixB的混合,用類型判讀is獲得的結果都是ture。但MixA與MixB直接卻並無直接關係,這也就符合了開閉原則,在不修改Child的基礎上經過mixin對其進行擴展。
咱們對例2稍加修改,就更接近Flutter App啓動過程的調用關係了:
//例3 class SuperClass{ SuperClass() { print('-->SuperClass init'); fun(); } fun(){ print('-->SuperClass.fun() start'); print('-->SuperClass.fun() end'); } } mixin MixA on SuperClass{ fun(){ print('-->MixA.fun() start'); super.fun(); print('-->MixA.fun() end'); } } mixin MixB on SuperClass{ fun(){ print('-->MixB.fun() start'); super.fun(); print('-->MixB.fun() end'); } } class Child extends SuperClass with MixA,MixB { Child() { print('-->Child init'); } } main(){ Child child = Child(); } 複製代碼
輸出結果:
-->SuperClass init -->MixB.fun() start -->MixA.fun() start -->SuperClass.fun() start -->SuperClass.fun() end -->MixA.fun() end -->MixB.fun() end -->Child init 複製代碼
至此,mixin機制的講解就先告一段落,這些都是便於咱們理解第2章講到的Flutter App啓動初始化過程。至於mixin其實還有其餘相關特性,沒有構造函數、with還能夠混入非mixin類等等,這裏就再也不展開了。
FlutterApp啓動過程在Android中主要是從
本文主要是結合mixin機制從main.dart中的main()開始,講解dart層面的初始化啓動過程
void main() => runApp(MyApp()); 複製代碼
接着是binding.dart中的runApp(),這裏是核心。這裏也是runApp啓動的三個主流程,咱們從這三行代碼來一一解析。
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) ..scheduleWarmUpFrame(); } 複製代碼
WidgetsFlutterBinding.ensureInitialized()其實就是一個獲取WidgetsFlutterBinding單例的過程,真正的初始化實現代碼在其7個mixin中。7個mixin分別完成不一樣部分的初始化工做,且根據mixin機制具備嚴格的前後調用鏈關係。至於這7個mixin的具體分工咱們後面再細說。
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,而mixin是沒有構造函數的。因此先執行了父類BindingBase構造函數。
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(); } 複製代碼
7個mixin都重寫了initInstances()方法,BindingBase.initInstances()會從最後混入的WidgetsBinding進行調用,而WidgetsBinding的initInstances函數中先經過super向上調用,屬於後續遍歷,因此調用順序和函數邏輯執行順序是相反的。回過頭看看第1章最後的例3,是否是很像。調用鏈如圖:
因爲是經過super實現了後序遍歷的調用,因此函數的邏輯執行順序是相反的,BindingBase的initInstances先執行,而後是GestureBinding...最後到WidgetsBinding,依次完成了各mixin的相關初始化工做。
(1)GestureBinding.initInstances 手勢事件綁定。進行一些變量初始化。GestureBinding中主要處理觸屏幕指針事件的分發以及事件最終回調處理。
void initInstances() { super.initInstances(); _instance = this; //將事件處理回調賦值給window,供window收到屏幕指針事件後調用 window.onPointerDataPacket = _handlePointerDataPacket; } 複製代碼
這裏將事件處理回調_handlePointerDataPacket賦值給window,供window收到屏幕指針事件後調用。window相似Android中的WindowManager,是framework層與engine層處理屏幕相關事件的橋樑。
發生屏幕指針事件後會回調window.onPointerDataPacket即這裏的_handlePointerDataPacket。_handlePointerDataPacket中會先調用hitTest進行命中測試。GestureBinding及RenderBinding都實現了hitTest方法,按照mixin順序會優先調用RenderBinding.hitTest。RenderBinding.hitTest會從renderTree的根節點遞歸調用命中測試,返回命中的深度最大的節點到根節點路徑上的全部節點。而後再執行dispatchEvent根據返回的hitTest命中節點列表遍歷分發事件,事件分發的順序是先子節點後父節點最終到根節點,相似前端的事件冒泡機制。
(2)ServicesBinding.initInstances Flutter與Platform通訊服務綁定。
void initInstances() { super.initInstances(); _instance = this; //構建一個_DefaultBinaryMessenger實例用於platform與flutter層通訊,消息信使 _defaultBinaryMessenger = createBinaryMessenger(); //window設置監聽回調,處理platform發送的消息 window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage; initLicenses(); //設置處理platform發送的系統消息 SystemChannels.system.setMessageHandler(handleSystemMessage); } 複製代碼
ServicesBinding主要就是platform與flutter層通訊相關服務的初始化,BinaryMessenger做爲兩者之間通訊的信使,在這裏被初始化,且一樣是交給window來處理消息。最後設置處理system消息handleSystemMessage,而ServicesBinding的handleSystemMessage是空實現,PaintingBinding及WidgetsBinding都實現了該方法。調用順序是WidgetsBinding.handleSystemMessage-->PaintingBinding.handleSystemMessage-->ServicesBinding.handleSystemMessage。一樣是經過super後續遍歷調用,先在PaintingBinding中處理系統字體變更事件,後在WidgetsBinding中處理系統發送的內存緊張信號。
(3)SchedulerBinding.initInstances 繪製調度綁定
void initInstances() { super.initInstances(); _instance = this; //設置AppLifecycleState生命週期回調 SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage); //根據生命週期變化設置window處理回調 //resumed || inactive狀態時才容許響應Vsync信號進行繪製 readInitialLifecycleStateFromNativeWindow(); //debug編譯模式時統計繪製流程時長,開始、運行、構建、光柵化。 if (!kReleaseMode) { int frameNumber = 0; addTimingsCallback((List<FrameTiming> timings) { for (final FrameTiming frameTiming in timings) { frameNumber += 1; _profileFramePostEvent(frameNumber, frameTiming); } }); } } 複製代碼
SchedulerBinding.initInstances 主要就是註冊監聽了flutter app的生命週期變化事件,根據生命週期狀態決定是否容許發起繪製任務。而SchedulerBinding的做用就是在window監聽到Vsync信號後,經過SchedulerBinding來發起繪製任務。
(4)PaintingBinding 繪製綁定。除了前面講的監聽系統字體變化事件,這裏主要是在繪製熱身幀以前預熱Skia渲染引擎。
void initInstances() { super.initInstances(); _instance = this; //初始化圖片緩存 _imageCache = createImageCache(); if (shaderWarmUp != null) { //第一幀繪製前的預熱工做 shaderWarmUp.execute(); } } 複製代碼
(5)SemanticsBinding.initInstances 渲染輔助類綁定。SemanticsBinding主要負責關聯語義樹與Flutter Engine。其實Flutter還維護一個semantic tree語義樹,頁面構建的時候會根據各Widget的語義描述構建一棵semantic tree語義樹。例如咱們在Image組件中配置semanticLabel語義內容,用戶在IOS/Android手機開啓無障礙功能時,觸摸到該Image時經過語義樹查找到對應的語義描述交給Flutter Engine,實現讀屏等功能。
void initInstances() { super.initInstances(); _instance = this; _accessibilityFeatures = window.accessibilityFeatures; } 複製代碼
(6)RendererBinding.initInstances 渲染綁定,RendererBinding是render tree 與 Flutter engine的粘合劑,由於它持有了render tree的根節點renderView。
void initInstances() { super.initInstances(); _instance = this; //初始化PipelineOwner管理渲染流程 _pipelineOwner = PipelineOwner( onNeedVisualUpdate: ensureVisualUpdate, onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, ); //設置window回調,處理屏幕參數、文本縮放因子、亮度等變化時回調。 window ..onMetricsChanged = handleMetricsChanged ..onTextScaleFactorChanged = handleTextScaleFactorChanged ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged ..onSemanticsAction = _handleSemanticsAction; //初始化一個RenderView做爲render tree的根節點,做爲渲染流水線執行入口 initRenderView(); //設置是否根據render tree生成語義樹 _handleSemanticsEnabledChanged(); assert(renderView != null); //繪製流水線回調 addPersistentFrameCallback(_handlePersistentFrameCallback); initMouseTracker();//鼠標監聽 } 複製代碼
回過頭看看(1)GestureBinding.initInstances方法中的事件處理,調用的就是這裏的renderView.hitTest從根節點開始命中測試的。正由於RenderBinding建立並持有了RenderView實例,因此GestureBinding中經過mixin機制將RenderBinding的hitTest方法混入,從而能夠實現命中測試,至關於須要用到命中測試的地方都經過mixin委託給RenderBinding來實現了。
addPersistentFrameCallback將繪製處理回調_handlePersistentFrameCallback加入到Persistent類型回調列表,_handlePersistentFrameCallback中的drawFrame方法是實現繪製流水線的地方,包括佈局和繪製流程,後面繪製熱身幀會用到。
(7)WidgetsBinding.initInstances 組件綁定
void initInstances() { super.initInstances(); _instance = this; assert(() { _debugAddStackFilters(); return true; }()); //初始化BuildOwnder,處理須要繪製的Element的構建工做 _buildOwner = BuildOwner(); //經過SchedulerBinding初始化window的onBeginFrame、onDrawFrame回調 //若是app可見,經過window.scheduleFrame向engine發起繪製請求 buildOwner.onBuildScheduled = _handleBuildScheduled; //語言環境變化處理 window.onLocaleChanged = handleLocaleChanged; //platform訪問權限變化處理 window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; //處理系統發送的push/pop頁面請求 SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator); } 複製代碼
WidgetsBinding屬於最外層的mixin,做爲處理Widget相關事件的入口。在初始化過程當中主要是生成了BuildOwner實例,以及window的onBeginFrame、onDrawFrame回調,後面渲染流程會用到。
BindingBase先經過按順序執行7個mixin的initInstances方法,完成了相關初始化工做,以及兩個重要類的實例化PipelineOwner、BuildOwner。而後就是執行了initServiceExtensions方法,實現了該方法的mixin按調用順序爲WidgetsBinding-->RendererBinding-->SchedulerBinding-->ServicesBinding主要就是在debug模式下注冊相關拓展服務。
ensureInitialized完成後,就開始執行scheduleAttachRootWidget(app)將用戶傳入的Widget綁定到一個跟節點並構建三棵樹。
void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } 複製代碼
因爲是組件相關,attachRootWidget具體的實如今WidgetsBinding裏
void attachRootWidget(Widget rootWidget) { _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>); } 複製代碼
相似Android中將DecorView與ViewRootImpl綁定,經過ViewRootImpl來做爲視圖操做根節點入口。Flutter中也是將app的主widget(即用戶定義的MyApp)和根節點綁定。其中render tree的根節點就是前面初始化流程中RendererBinding.initInstances過程建立的RenderView,RenderView是繼承自RenderObject的,因此還須要建立Element和Widget與之關聯,而建立的Element和Widget分別對應另外兩棵樹的根節點。
(1)先是經過傳入的MyApp及RenderView實例化了一個RenderObjectToWidgetAdapter對象,而RenderObjectToWidgetAdapter是繼承自RenderObjectWidget,即建立了Widget樹的根節點。
(2)createElement建立根element,並經過BuildOwner構建須要構建的element
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { owner.lockState(() { //建立了一個RenderObjectToWidgetElement實例做爲element tree的根節點 element = createElement(); assert(element != null); //綁定BuildOwner element.assignOwner(owner); }); //標記須要構建的element,並rebuild owner.buildScope(element, () { element.mount(null, null); }); SchedulerBinding.instance.ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element; } 複製代碼
綁定完根節點後,就開始當即執行scheduleWarmUpFrame()繪製首幀的工做了。不調用scheduleWarmUpFrame(),按理說也是能夠渲染的,那熱身幀的意義是什麼呢?。
和普通繪製同樣,熱身幀也是經過handleBeginFrame、handleDrawFrame這兩個回調來進行繪製流程,在前面WidgetBinding初始化時將這兩個回調交給了window,具體代碼邏輯是在SchedulerBinding。
void scheduleWarmUpFrame() { if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) return; _warmUpFrame = true; Timeline.startSync('Warm-up frame'); final bool hadScheduledFrame = _hasScheduledFrame; // Timer任務會加入到event queue // 因此在執行繪製前先處理完microtask queue中的任務 Timer.run(() { assert(_warmUpFrame); // 繪製Frame前工做,主要是處理Animate動畫 handleBeginFrame(null); }); // 繪製前有機會執行完microtask queue Timer.run(() { assert(_warmUpFrame); // 開始Frame繪製 handleDrawFrame(); resetEpoch(); _warmUpFrame = false; if (hadScheduledFrame) //後續Frame繪製請求 scheduleFrame(); }); lockEvents(() async { await endOfFrame; Timeline.finishSync(); }); } 複製代碼
handleBeginFrame處理動畫相關邏輯,動畫回調後並不當即執行動畫,而是改變了animation.value,並調用setSate()來發起繪製請求。動畫的過程就是在Vsync信號到來時根據動畫進度計算出對應的value,而對應的Widget也會隨着animation.value的變化而重建,從而造成動畫,是否是和Android的屬性動畫原理差很少。
void handleBeginFrame(Duration rawTimeStamp) { ... _hasScheduledFrame = false; try { // 處理回調前設置爲瞬態 _schedulerPhase = SchedulerPhase.transientCallbacks; final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks; _transientCallbacks = <int, _FrameCallbackEntry>{}; //處理Animation回調 callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { if (!_removedIds.contains(id)) _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack); }); _removedIds.clear(); } finally { //回調處理完,設置爲中間態,即先處理microTask任務隊列 _schedulerPhase = SchedulerPhase.midFrameMicrotasks; } } 複製代碼
handleBeginFrame處理完後,會優先處理microTask任務隊列。而後纔是event Task,window.onDrawFrame(),對應SchedulerBinding.handleDrawFrame()。(Timer任務會加入到event queue,flutter的事件處理機制是優先處理micro queue中任務)
void handleDrawFrame() { try { // 處理Persistent類型回調,主要包括build\layout\draw流程 _schedulerPhase = SchedulerPhase.persistentCallbacks; for (FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); // 處理Post-Frame回調,主要是狀態清理,準備調度下一幀繪製請求 _schedulerPhase = SchedulerPhase.postFrameCallbacks; final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); } finally { //處理完成,狀態idle _schedulerPhase = SchedulerPhase.idle; _currentFrameTimeStamp = null; } } 複製代碼
WidgetsBinding.drawFrame()爲Persistent類型的一個回調,在前面講到的RendererBinding初始化時經過addPersistentFrameCallback中加入了RendererBinding.drawFrame,因此這裏也是用到了mixin機制,在WidgetsBinding.drawFrame()中完成組件的構建任務,在RendererBinding.drawFrame完成組件的佈局、繪製任務。是否是分工明確。
//WidgetsBinding.drawFrame() void drawFrame() { try { if (renderViewElement != null) //調用BuildOwner.buildScope開始構建 buildOwner.buildScope(renderViewElement); //調用RendererBinding.drawFrame,開始佈局、繪製階段。 super.drawFrame(); //從element tree中移除不須要的element,unmount buildOwner.finalizeTree(); } finally { ... } } 複製代碼
繪製流程結束後會產生這一幀的數據Scene,由window.render交給Engine,最終顯示到屏幕。整個熱身幀繪製流程如圖:
(1) mixin機制在FlutterApp啓動過程可謂秀的飛起,經過如上分析也獲得了mixin機制帶來的優點有哪些:
(2)Flutter App的啓動過程總結:
參考文章:
[1] Dart: What are mixins?
[2] Dart 2 Mixin Declarations
[3] 完全理解 Dart mixin 機制