說到網絡與通訊,就不得不提到異步編程。所謂異步編程,就是一種非阻塞的、事件驅動的編程機制,它能夠充分利用系統資源來並行執行多個任務,所以提升了系統的運行效率。前端
事件循環是Dart中處理事件的一種機制,與Android中的Handler消息傳遞機制和前端的eventloop事件循環機制有點相似。在Flutter開發中,Flutter就是經過事件循環來驅動程序運行的。
衆所周知,Dart是一種單線程模型運行語言,這意味着Dart在同一時刻只能執行一個操做,其餘操做須要在該操做執行完成以後才能執行,而多個操做的執行須要經過Dart的事件驅動模型,其運行流程以下圖所示。編程
入口main()函數執行完成以後,消息循環機制便啓動了。Dart程序在啓動時會建立兩個隊列,一個是微任務隊列,另外一個是事件隊列,而且微任務隊列的執行優先級高於事件隊列。
首先,事件循環模型會按照先進先出的順序逐個執行微任務隊列中的任務,當全部微任務隊列執行完後便開始執行事件隊列中的任務,事件任務執行完畢後再去執行微任務,如此循環往復,直到應用退出。
在Dart中,全部的外部事件任務都在事件隊列中,如IO、計時器、點擊、以及繪製事件等,而微任務則一般來源於Dart內部,而且微任務很是少。之因此如此,是由於微任務隊列優先級高,若是微任務太多,那麼執行時間總和就越久,事件隊列任務的延遲也就越久。而對於GUI應用來講,最直觀的表現就是比較卡,因此Dart的事件循環模型必須保證微任務隊列不能太耗時。
因爲Dart是一種單線程模型語言,因此當某個任務發生異常且沒有被捕獲時,程序並不會退出,而是直接阻塞當前任務後續代碼的執行,可是並不會阻塞其餘任務的執行,也就是說一個任務的異常是不會影響其它任務的執行。
能夠看出,將任務加入到微任務中能夠被儘快執行,但也須要注意,當事件循環在處理微任務隊列時,事件隊列會被卡住,此時應用程序沒法處理鼠標單擊、I/O消息等事件。同時,當事件循壞出現異常時,也可使用Dart提供的try/catch/finally來捕獲異常,並跳過異常執行其餘事件。安全
在Flutter開發中,常常會遇到耗時操做的場景,因爲Dart是基於單線程模型的語言,因此耗時操做每每會堵塞其餘代碼的執行。爲了解決這一問題,Dart提供了併發機制,即Isolate。
所謂Isolate,實際上是Dart中的一個線程,不過與Java中的線程實現方式有所不一樣,Isolate是經過Flutter的Engine層建立出來的,Dart代碼默認運行在主的Isolate上。當Dart代碼處於運行狀態時,同一個Isolate中的其餘代碼是沒法運行的。Flutter能夠擁有多個Isolates,但多個Isolates之間不能共享內存,不一樣Isolate之間能夠經過消息機制來進行通訊。
同時,每一個Isolate都擁有屬於本身的事件循環及消息隊列,這意味着在一個Isolate中運行的代碼與另一個Isolate中的代碼不存在任何關聯。也正是由於這一特性,才讓Dart具備了並行處理的能力。
默認狀況下,Isolate是經過Flutter的Engine層建立出來的,Dart代碼默認運行在主Isolate上,必要時還可使用系統提供的API來建立新的Isolate,以便更好的利用系統資源,如主線程過載時。
在Dart中,建立Isolate主要有spawnUri和spawn兩種方式。與Isolate相關的代碼都在isolate.dart文件中,spawnUri的構造函數以下所示。網絡
external static Future<Isolate> spawnUri( Uri uri, List<String> args, var message, {bool paused: false, SendPort onExit, SendPort onError, bool errorsAreFatal, bool checked, Map<String, String> environment, @Deprecated('The packages/ dir is not supported in Dart 2') Uri packageRoot, Uri packageConfig, bool automaticPackageResolution: false, @Since("2.3") String debugName});
使用spawnUri方式建立Isolate時有三個必傳參數,分別是Uri、args和messag。其中,Uri用於指定一個新Isolate代碼文件的路徑,args用於表示參數列表,messag表示須要發送的動態消息。
須要注意的是,用於運行新Isolate的代碼文件必須包含一個main函數,它是新建立的Isolate的入口方法,而且main函數中的args參數與spawnUri中的args參數對應。若是不須要向新的Isolate中傳遞參數,能夠向該參數傳遞一個空列表。首先,使用IntelliJ IDEA新建一個Dart工程,而後在主Isolate中添加以下代碼。架構
import 'dart:isolate'; void main(List<String> arguments) { print("main isolate start"); createIsolate(); print("main isolate stop"); } createIsolate() async{ ReceivePort rp = new ReceivePort(); SendPort port = rp.sendPort; Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_isolate.dart"), ["hello Isolate", "this is args"], port); SendPort sendPort; rp.listen((message){ print("main isolate message: $message"); if (message[0] == 0){ sendPort = message[1]; }else{ sendPort?.send([1,"這條信息是main Isolate發送的"]); } }); }
而後,在主Isolate文件的同級目錄下新建一個other_isolate.dart文件,代碼以下。併發
import 'dart:isolate'; import 'dart:io'; void main(args, SendPort sendPort) { print("child isolate start"); print("child isolate args: $args"); ReceivePort receivePort = new ReceivePort(); SendPort port = receivePort.sendPort; receivePort.listen((message){ print("child_isolate message: $message"); }); sendPort.send([0, port]); sleep(Duration(seconds:5)); sendPort.send([1, "child isolate 任務完成"]); print("child isolate stop"); }
運行主Isolate文件代碼,最終的輸出結果以下。異步
main isolate start main isolate stop child isolate start child isolate args: [hello Isolate, this is args] main isolate message: [0, SendPort] child isolate stop main isolate message: [1, child isolate 任務完成] child_isolate message: [1, 這條信息是main Isolate發送的]
在Dart中,多個Isolate之間的通訊是經過ReceivePort來完成的。而ReceivePort能夠認爲是消息管道,當消息的傳遞方向時固定的,經過這個管道就能把消息發送給接收端。
除了使用spawnUri外,更經常使用的方式是使用spawn來建立Isolate,spawn的構造函數以下。async
external static Future<Isolate> spawn<T>( void entryPoint(T message), T message, {bool paused: false, bool errorsAreFatal, SendPort onExit, SendPort onError});
使用spawn方式建立Isolate時須要傳遞兩個參數,即函數entryPoint和參數message。entryPoint表示新建立的Isolate的耗時函數,message表示是動態消息,該參數一般用於傳送主Isolate的SendPort對象。
一般,使用spawn方式建立Isolate時,咱們但願將新建立的Isolate代碼和主Isolate代碼寫在同一個文件,且不但願出現兩個main函數,而且將耗時函數運行在新的Isolate,這樣作的目的是有利於代碼的組織與複用。異步編程
import 'dart:isolate'; Future<void> main(List<String> arguments) async { print(await asyncFibonacci(20)); //計算20的階乘 } Future<dynamic> asyncFibonacci(int n) async{ final response = new ReceivePort(); await Isolate.spawn(isolate,response.sendPort); final sendPort = await response.first as SendPort; final answer = new ReceivePort(); sendPort.send([n,answer.sendPort]); return answer.first; } void isolate(SendPort initialReplyTo){ final port = new ReceivePort(); initialReplyTo.send(port.sendPort); port.listen((message){ final data = message[0] as int; final send = message[1] as SendPort; send.send(syncFibonacci(data)); }); } int syncFibonacci(int n){ return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1); }
在上面的代碼中,耗時的操做放在使用spawn方法建立的Isolate中。運行上面的程序,最終的輸出結果爲6765,即20的階乘。函數
默認狀況下,Flutter Engine層會建立一個Isolate,而且Dart代碼默認就運行在這個主Isolate上。必要時可使用spawnUri和spawn兩種方式來建立新的Isolate,在Flutter中,新建立的Isolate由Flutter進行統一的管理。
事實上,Flutter Engine本身不建立和管理線程,Flutter Engine線程的建立和管理是Embeder負責的,Embeder指的是將引擎移植到平臺的中間層代碼,Flutter Engine層的架構示意圖以下圖所示。
在Flutter的架構中,Embeder提供四個Task Runner,分別是Platform Task Runner、UI Task Runner Thread、GPU Task Runner和IO Task Runner,每一個Task Runner負責不一樣的任務,Flutter Engine不在意Task Runner運行在哪一個線程,可是它須要線程在整個生命週期裏面保持穩定。
Platform Task Runner是Flutter Engine的主Task Runner,相似於Android或者iOS的Main Thread。不過它們之間仍是有區別的,通常來講,一個Flutter應用啓動的時候會建立一個Engine實例,Engine建立的時候會建立一個線程供Platform Runner使用。
同時,跟Flutter Engine的全部交互都必須在Platform Thread中進行,若是試圖在其它線程中調用Flutter Engine可能會出現沒法預期的異常,這跟iOS和Android中對於UI的操做都必須發生在主線程的道理相似。須要注意的是,Flutter Engine中有不少模塊都是非線程安全的,所以對於Flutter Engine的接口調用都需保證在Platform Thread進行。
雖然阻塞Platform Thread不會直接致使Flutter應用的卡頓,可是也不建議在這個主Runner執行繁重的操做,由於長時間卡住Platform Thread有可能會被系統的Watchdog程序強殺。
UI Task Runner用於執行Root Isolate代碼,它運行在線程對應平臺的線程上,屬於子線程。同時,Root isolate在引擎啓動時會綁定了很多Flutter須要的函數方法,以便進行渲染操做。
對於每一幀,引擎經過Root Isolate通知Flutter Engine有幀須要渲染,平臺收到Flutter Engine通知後會建立對象和組件並生成一個Layer Tree,而後將生成的Layer Tree提交給Flutter Engine。此時,只生成了須要繪製的內容,並無執行屏幕渲染,而Root Isolate就是負責將建立的Layer Tree繪製到屏幕上,所以若是線程過載會致使卡頓掉幀。
除了用於處理渲染以外,Root Isolate還須要處理來自Native Plugins的消息響應、Timers、MicroTasks和異步IO。若是確實有沒法避免的繁重計算,建議將這些耗時的操做放到獨立的Isolate去執行,從而避免應用UI卡頓問題。
GPU Task Runner用於執行設備GPU指令,UI Task Runner建立的Layer Tree是跨平臺的。也就是說,Layer Tree提供了繪製所須要的信息,可是由由誰來完成繪製它是不關心的。
GPU Task Runner負責將Layer Tree提供的信息轉化爲平臺可執行的GPU指令,GPU Task Runner同時也負責管理每一幀繪製所須要的GPU資源,包括平臺Framebuffer的建立,Surface生命週期管理,以及Texture和Buffers的繪製時機等。
通常來講,UI Runner和GPU Runner運行在不一樣的線程。GPU Runner會根據目前幀執行的進度去向UI Runner請求下一幀的數據,在任務繁重的時候還可能會出現UI Runner的延遲任務。不過這種調度機制的好處在於,確保GPU Runner不至於過載,同時也避免了UI Runner沒必要要的資源消耗。
GPU Runner能夠致使UI Runner的幀調度的延遲,GPU Runner的過載會致使Flutter應用的卡頓,所以在實際使用過程當中,建議爲每個Engine實例都新建一個專用的GPU Runner線程。
IO Task Runner也運行在平臺對應的子線程中,主要做用是作一些預先處理的讀取操做,爲GPU Runner的渲染操做作準備。咱們能夠認爲IO Task Runner是GPU Task Runner的助手,它能夠減小GPU Task Runner的額外工做。例如,在Texture的準備過程當中,IO Runner首先會讀取壓縮的圖片二進制數據,並將其解壓轉換成GPU可以處理的格式,而後再將數據傳遞給GPU進行渲染。
雖然IO Task Runner並不會直接致使Flutter應用的卡頓,可是可能會致使圖片和其它一些資源加載的延遲,並間接影響應用性能,因此建議將IO Runner放到一個專用的線程中。
Dart的Isolate是Dart虛擬機建立和管理的,Flutter Engine沒法直接訪問。Root Isolate經過Dart的C++調用能力把UI渲染相關的任務提交到UI Runner執行, 這樣就能夠跟Flutter Engine模塊進行交互,Flutter UI的任務也被提交到UI Runner,並能夠給Isolate發送一些事件通知,UI Runner同時也能夠處理來自應用的Native Plugin任務。
總的來講,Dart Isolate跟Flutter Runner是相互獨立的,它們經過任務調度機制相互協做。