<簡書 — 劉小壯> https://www.jianshu.com/p/54da18ed1a9egit
Flutter
默認是單線程任務處理的,若是不開啓新的線程,任務默認在主線程中處理。github
和iOS應用很像,在Dart
的線程中也存在事件循環和消息隊列的概念,但在Dart
中線程叫作isolate
。應用程序啓動後,開始執行main
函數並運行main isolate
。編程
每一個isolate
包含一個事件循環以及兩個事件隊列,event loop
事件循環,以及event queue
和microtask queue
事件隊列,event
和microtask
隊列有點相似iOS的source0
和source1
。json
isolate
消息等外部事件。isolate
內部添加事件,事件的優先級比event queue
高。這兩個隊列也是有優先級的,當isolate
開始執行後,會先處理microtask
的事件,當microtask
隊列中沒有事件後,纔會處理event
隊列中的事件,並按照這個順序反覆執行。但須要注意的是,當執行microtask
事件時,會阻塞event
隊列的事件執行,這樣就會致使渲染、手勢響應等event
事件響應延時。爲了保證渲染和手勢響應,應該儘可能將耗時操做放在event
隊列中。安全
在異步調用中有三個關鍵詞,async
、await
、Future
,其中async
和await
須要一塊兒使用。在Dart
中能夠經過async
和await
進行異步操做,async
表示開啓一個異步操做,也能夠返回一個Future
結果。若是沒有返回值,則默認返回一個返回值爲null
的Future
。網絡
async
、await
本質上就是Dart
對異步操做的一個語法糖,能夠減小異步調用的嵌套調用,而且由async
修飾後返回一個Future
,外界能夠以鏈式調用的方式調用。這個語法是JS
的ES7
標準中推出的,Dart
的設計和JS
相同。架構
下面封裝了一個網絡請求的異步操做,而且將請求後的Response
類型的Future
返回給外界,外界能夠經過await
調用這個請求,並獲取返回數據。從代碼中能夠看到,即使直接返回一個字符串,Dart
也會對其進行包裝併成爲一個Future
。併發
Future<Response> dataReqeust() async {
String requestURL = 'https://jsonplaceholder.typicode.com/posts';
Client client = Client();
Future<Response> response = client.get(requestURL);
return response;
}
Future<String> loadData() async {
Response response = await dataReqeust();
return response.body;
}
複製代碼
在代碼示例中,執行到loadData
方法時,會同步進入方法內部進行執行,當執行到await
時就會中止async
內部的執行,從而繼續執行外面的代碼。當await
有返回後,會繼續從await
的位置繼續執行。因此await
的操做,不會影響後面代碼的執行。框架
下面是一個代碼示例,經過async
開啓一個異步操做,經過await
等待請求或其餘操做的執行,並接收返回值。當數據發生改變時,調用setState
方法並更新數據源,Flutter
會更新對應的Widget
節點視圖。異步
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
複製代碼
Future
就是延時操做的一個封裝,能夠將異步任務封裝爲Future
對象。獲取到Future
對象後,最簡單的方法就是用await
修飾,並等待返回結果繼續向下執行。正如上面async、await
中講到的,使用await
修飾時須要配合async
一塊兒使用。
在Dart
中,和時間相關的操做基本都和Future
有關,例如延時操做、異步操做等。下面是一個很簡單的延時操做,經過Future
的delayed
方法實現。
loadData() {
// DateTime.now(),獲取當前時間
DateTime now = DateTime.now();
print('request begin $now');
Future.delayed(Duration(seconds: 1), (){
now = DateTime.now();
print('request response $now');
});
}
複製代碼
Dart
還支持對Future
的鏈式調用,經過追加一個或多個then
方法來實現,這個特性很是實用。例如一個延時操做完成後,會調用then
方法,而且能夠傳遞一個參數給then
。調用方式是鏈式調用,也就表明能夠進行不少層的處理。這有點相似於iOS的RAC
框架,鏈式調用進行信號處理。
Future.delayed(Duration(seconds: 1), (){
int age = 18;
return age;
}).then((onValue){
onValue++;
print('age $onValue');
});
複製代碼
若是想要了解async
、await
的原理,就要先了解協程的概念,async
、await
本質上就是協程的一種語法糖。協程,也叫做coroutine
,是一種比線程更小的單元。若是從單元大小來講,基本能夠理解爲進程->線程->協程。
在弄懂協程以前,首先要明白併發和並行的概念,併發指的是由系統來管理多個IO的切換,並交由CPU去處理。並行指的是多核CPU在同一時間裏執行多個任務。
併發的實現由非阻塞操做+事件通知來完成,事件通知也叫作「中斷」。操做過程分爲兩種,一種是CPU對IO進行操做,在操做完成後發起中斷告訴IO操做完成。另外一種是IO發起中斷,告訴CPU能夠進行操做。
線程本質上也是依賴於中斷來進行調度的,線程還有一種叫作「阻塞式中斷」,就是在執行IO操做時將線程阻塞,等待執行完成後再繼續執行。但線程的消耗是很大的,並不適合大量併發操做的處理,而經過單線程併發能夠進行大量併發操做。當多核CPU出現後,單個線程就沒法很好的利用多核CPU的優點了,因此又引入了線程池的概念,經過線程池來管理大量線程。
在程序執行過程當中,離開當前的調用位置有兩種方式,繼續調用其餘函數和return
返回離開當前函數。可是執行return
時,當前函數在調用棧中的局部變量、形參等狀態則會被銷燬。
協程分爲無線協程和有線協程,無線協程在離開當前調用位置時,會將當前變量放在堆區,當再次回到當前位置時,還會繼續從堆區中獲取到變量。因此,通常在執行當前函數時就會將變量直接分配到堆區,而async
、await
就屬於無線協程的一種。有線協程則會將變量繼續保存在棧區,在回到指針指向的離開位置時,會繼續從棧中取出調用。
以async
、await
爲例,協程在執行時,執行到async
則表示進入一個協程,會同步執行async
的代碼塊。async
的代碼塊本質上也至關於一個函數,而且有本身的上下文環境。當執行到await
時,則表示有任務須要等待,CPU則去調度執行其餘IO,也就是後面的代碼或其餘協程代碼。過一段時間CPU就會輪訓一次,看某個協程是否任務已經處理完成,有返回結果能夠被繼續執行,若是能夠被繼續執行的話,則會沿着上次離開時指針指向的位置繼續執行,也就是await
標誌的位置。
因爲並無開啓新的線程,只是進行IO中斷改變CPU調度,因此網絡請求這樣的異步操做可使用async
、await
,但若是是執行大量耗時同步操做的話,應該使用isolate
開闢新的線程去執行。
若是用協程和iOS的dispatch_async
進行對比,能夠發現兩者是比較類似的。從結構定義來看,協程須要將當前await
的代碼塊相關的變量進行存儲,dispatch_async
也能夠經過block
來實現臨時變量的存儲能力。
我以前還在想一個問題,蘋果爲何不引入協程的特性呢?後來想了一下,await
和dispatch_async
均可以簡單理解爲異步操做,OC的線程是基於Runloop
實現的,Dart
本質上也是有事件循環的,並且兩者都有本身的事件隊列,只是隊列數量和分類不一樣。
我以爲當執行到await
時,保存當前的上下文,並將當前位置標記爲待處理任務,用一個指針指向當前位置,並將待處理任務放入當前isolate
的隊列中。在每一個事件循環時都去詢問這個任務,若是須要進行處理,就恢復上下文進行任務處理。
這裏想提一下JS
裏的Promise
語法,在iOS中會出現不少if
判斷或者其餘的嵌套調用,而Promise
能夠把以前橫向的嵌套調用,改爲縱向鏈式調用。若是能把Promise
引入到OC裏,可讓代碼看起來更簡潔,直觀。
isolate
是Dart
平臺對線程的實現方案,但和普通Thread
不一樣的是,isolate
擁有獨立的內存,isolate
由線程和獨立內存構成。正是因爲isolate
線程之間的內存不共享,因此isolate
線程之間並不存在資源搶奪的問題,因此也不須要鎖。
經過isolate
能夠很好的利用多核CPU,來進行大量耗時任務的處理。isolate
線程之間的通訊主要經過port
來進行,這個port
消息傳遞的過程是異步的。經過Dart
源碼也能夠看出,實例化一個isolate
的過程包括,實例化isolate
結構體、在堆中分配線程內存、配置port
等過程。
isolate
看起來其實和進程比較類似,以前請教阿里架構師宗心問題時,宗心也說過「isolate
的總體模型我本身的理解其實更像進程,而async
、await
更像是線程」。若是對比一下isolate
和進程的定義,會發現確實isolate
很像是進程。
下面是一個isolate
的例子,例子中新建立了一個isolate
,而且綁定了一個方法進行網絡請求和數據解析的處理,並經過port
將處理好的數據返回給調用方。
loadData() async {
// 經過spawn新建一個isolate,並綁定靜態方法
ReceivePort receivePort =ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 獲取新isolate的監聽port
SendPort sendPort = await receivePort.first;
// 調用sendReceive自定義方法
List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
print('dataList $dataList');
}
// isolate的綁定方法
static dataLoader(SendPort sendPort) async{
// 建立監聽port,並將sendPort傳給外界用來調用
ReceivePort receivePort =ReceivePort();
sendPort.send(receivePort.sendPort);
// 監聽外界調用
await for (var msg in receivePort) {
String requestURL =msg[0];
SendPort callbackPort =msg[1];
Client client = Client();
Response response = await client.get(requestURL);
List dataList = json.decode(response.body);
// 回調返回值給調用者
callbackPort.send(dataList);
}
}
// 建立本身的監聽port,而且向新isolate發送消息
Future sendReceive(SendPort sendPort, String url) {
ReceivePort receivePort =ReceivePort();
sendPort.send([url, receivePort.sendPort]);
// 接收到返回值,返回給調用者
return receivePort.first;
}
複製代碼
isolate
和iOS中的線程還不太同樣,isolate
的線程更偏底層。當生成一個isolate
後,其內存是各自獨立的,相互之間並不能進行訪問。但isolate
提供了基於port
的消息機制,經過創建通訊雙方的sendPort
和receiveport
,進行相互的消息傳遞,在Dart
中叫作消息傳遞。
從上面例子中能夠看出,在進行isolate
消息傳遞的過程當中,本質上就是進行port
的傳遞。將port
傳遞給其餘isolate
,其餘isolate
經過port
拿到sendPort
,向調用方發送消息來進行相互的消息傳遞。
正如其名,Embedder
是一個嵌入層,將Flutter
嵌入到各個平臺上。Embedder
負責範圍包括原平生臺插件、線程管理、事件循環等。
Embedder
中存在四個Runner
,四個Runner
分別以下。其中每一個Flutter Engine
各自對應一個UI Runner
、GPU Runner
、IO Runner
,但全部Engine
共享一個Platform Runner
。
Runner
和isolate
並非一碼事,彼此相互獨立。以iOS平臺爲例,Runner
的實現就是CFRunLoop
,以一個事件循環的方式不斷處理任務。而且Runner
不僅處理Engine
的任務,還有Native Plugin
帶來的原平生臺的任務。而isolate
則由Dart VM
進行管理,和原平生臺線程並沒有關係。
Platform Runner
和iOS平臺的Main Thread
很是類似,在Flutter
中除耗時操做外,全部任務都應該放在Platform
中,Flutter
中的不少API並非線程安全的,放在其餘線程中可能會致使一些bug。
但例如IO之類的耗時操做,應該放在其餘線程中完成,不然會影響Platform
的正常執行,甚至於被watchdog
幹掉。但須要注意的是,因爲Embedder Runner
的機制,Platform
被阻塞後並不會致使頁面卡頓。
不僅是Flutter Engine
的代碼在Platform
中執行,Native Plugin
的任務也會派發到Platform
中執行。實際上,在原生側的代碼運行在Platform Runner
中,而Flutter
側的代碼運行在Root Isolate
中,若是在Platform
中執行耗時代碼,則會卡原平生臺的主線程。
UI Runner
負責爲Flutter Engine
執行Root Isolate
的代碼,除此以外,也處理來自Native Plugin
的任務。Root Isolate
爲了處理自身事件,綁定了不少函數方法。程序啓動時,Flutter Engine
會爲Root
綁定UI Runner
的處理函數,使Root Isolate
具有提交渲染幀的能力。
當Root Isolate
向Engine
提交一次渲染幀時,Engine
會等待下次vsync,當下次vsync到來時,由Root Isolate
對Widgets
進行佈局操做,並生成頁面的顯示信息的描述,並將信息交給Engine
去處理。
因爲對widgets
進行layout
並生成layer tree
是UI Runner
進行的,若是在UI Runner
中進行大量耗時處理,會影響頁面的顯示,因此應該將耗時操做交給其餘isolate
處理,例如來自Native Plugin
的事件。
GPU Runner
並不直接負責渲染操做,其負責GPU相關的管理和調度。當layer tree
信息到來時,GPU Runner
將其提交給指定的渲染平臺,渲染平臺是Skia配置的,不一樣平臺可能有不一樣的實現。
GPU Runner
相對比較獨立,除了Embedder
外其餘線程均不可向其提交渲染信息。
一些GPU Runner
中比較耗時的操做,就放在IO Runner
中進行處理,例如圖片讀取、解壓、渲染等操做。可是隻有GPU Runner
才能對GPU提交渲染信息,爲了保證IO Runner
也具有這個能力,因此IO Runner
會引用GPU Runner
的context
,這樣就具有向GPU提交渲染信息的能力。
簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github
上,下載Flutter編程指南 PDF
合集。把全部Flutter
文章總計三篇,都寫在這個PDF
中,並且左側有目錄,方便閱讀。
下載地址:Flutter編程指南 PDF 麻煩各位大佬點個贊,謝謝!😁