1. 什么是Ioslate
我們的flutter應(yīng)用啟動(dòng)的時(shí)候就會(huì)開辟一個(gè)獨(dú)立的ioslate,這里面包含了一個(gè)獨(dú)立的內(nèi)存空間和一個(gè)攜帶 event loops的單一線程垛吗,這個(gè)單一線程只處理事件循環(huán)咏窿,我們所有dart代碼都在ioslate里面執(zhí)行息拜,所有的事件廉丽,例如布局構(gòu)建和拆除缠黍,異步任務(wù),io事件等都是在這里面執(zhí)行药蜻,每個(gè)事件都會(huì)被加入到一個(gè)事件隊(duì)列中瓷式,由event loops從隊(duì)列中按照先進(jìn)先出的方式取出事件依次執(zhí)行替饿。(和android的handler機(jī)制挺像的嘛)
如上圖所示,就代表一個(gè)獨(dú)立的ioslate 它就像我們機(jī)器上面開辟的一個(gè)小空間贸典,綠色部分就代表一個(gè)獨(dú)立的內(nèi)存空間视卢,紅色部就代表事件循環(huán),在這種方式下廊驼,我們是不能直接在dart中做大數(shù)據(jù)量的計(jì)算的据过,寫flutter的開發(fā)者基本都是從android或ios轉(zhuǎn)過來的,在android那邊妒挎,對(duì)于大數(shù)據(jù)量的計(jì)算绳锅,為了不阻塞ui線程,我們可以使用開啟異步任務(wù)執(zhí)行大數(shù)據(jù)量的計(jì)算酝掩,但是在dart這邊是不能這么做的鳞芙,因?yàn)閐art默認(rèn)所有代碼都在默認(rèn)的ioslate中執(zhí)行,而異步任務(wù)async對(duì)于ioslate來說也只是一個(gè)事件而已期虾,而事件循環(huán)只有在線程空閑的時(shí)候才會(huì)從事件隊(duì)列中取出事件執(zhí)行原朝,大數(shù)據(jù)量的計(jì)算導(dǎo)致線程不空閑,從而阻塞事件隊(duì)列镶苞,導(dǎo)致應(yīng)用掉幀喳坠,對(duì)于這個(gè)問題,我們可以使用Isolate.spawn()
或Flutter's compute()
函數(shù)新建獨(dú)立的ioslate執(zhí)行大數(shù)據(jù)量的計(jì)算茂蚓,讓我們的 main isolate可以有空閑的時(shí)間來處理小部件的重建和銷毀壕鹉。
這些新建的isolate雖然由main isolate創(chuàng)建,但是main ioslate卻不能直接訪問child ioslate的內(nèi)存煌贴,這就是ioslate的名字由來:這些小空間彼此隔離御板。
不同ioslate之間可以使用ReceivePort相互訪問,他們之間唯一的工作方式就是通過不停的消息傳遞將事件傳遞給對(duì)方牛郑,在將事件加入到自己的事件隊(duì)列中怠肋。
2. event loops
如圖所示,如果把應(yīng)用的生命周期比作一條時(shí)間線淹朋,那么在這條時(shí)間線中會(huì)發(fā)生各種事件笙各,例如點(diǎn)擊事件,widget的構(gòu)建和銷毀础芍,io事件等杈抢,而這些事件會(huì)在任何時(shí)刻以任意的組合方式出現(xiàn)在事件隊(duì)列中,event loops 會(huì)在空閑的時(shí)候從事件隊(duì)列中按照先進(jìn)先出的方式取出事件執(zhí)行仑性,一直到事件隊(duì)列被清空為止惶楼,當(dāng)事件隊(duì)列被清空,并且所有的事件都被執(zhí)行結(jié)束后,ioslate中的線程就會(huì)掛起歼捐,這個(gè)時(shí)候就可以執(zhí)行g(shù)c操作何陆,清除內(nèi)存。
而異步編程就是建立在這樣的基礎(chǔ)上的豹储,如下代碼所示:
RaisedButton( //1. a event to build RaisedButton
child: Text('Click me'),
onPressed: () { //2. a event to send onTap
final myFuture = http.get('https://example.com'); //3. a event to send request
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
在代碼中贷盲,發(fā)起了三個(gè)事件:
- 構(gòu)建一個(gè)RaisedButton
- 用戶發(fā)起的tap事件
- 發(fā)起一個(gè)異步任務(wù),請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
不管是構(gòu)建部件剥扣,還是異步任務(wù)巩剖,甚至異步任務(wù)的回調(diào),對(duì)于 event loops來說钠怯,他們都只是一個(gè)事件而已佳魔,所以event loops它不管接下來出現(xiàn)的是什么事件,它只管從事件隊(duì)列中取出事件執(zhí)行呻疹,只要線程處于空閑下吃引,它就不停的取出隊(duì)列中的任務(wù)執(zhí)行。上面的代碼會(huì)向 event loops中塞入一個(gè)構(gòu)建按鈕的事件刽锤,執(zhí)行完后等待用戶點(diǎn)擊事件的到來镊尺,當(dāng)用戶點(diǎn)擊事件到來后會(huì)告訴flutter點(diǎn)擊的坐標(biāo),flutter會(huì)根據(jù)坐標(biāo)從渲染系統(tǒng)中找出對(duì)應(yīng)的按鈕的onPressed屬性并思,執(zhí)行相應(yīng)的代碼庐氮,這個(gè)時(shí)候,又會(huì)發(fā)出一個(gè)網(wǎng)絡(luò)請(qǐng)求宋彼,并且注冊(cè)回調(diào)弄砍,但是這對(duì)于 event loops來說也就是一個(gè)事件而已, event loops只管執(zhí)行事件输涕,當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束后會(huì)發(fā)起回調(diào)音婶。
3. event loops處理的事件
- I/O
- gesture
- drawing
- timers
- streams
- futures
4. isolate(隔離區(qū))的使用和通信
1. 創(chuàng)建隔離區(qū)
可以使用Isolate.spawn()
或Flutter's compute()
函數(shù)新建新的隔離區(qū),這個(gè)過程中莱坎,會(huì)新建新的線程和event loops衣式,分配新的cpu資源和內(nèi)存區(qū)域,代碼如下
import 'dart:isolate';
void main() {
Isolate.spawn(isolate, "true");
}
void isolate(String data) {
print("isolate ${data}");
}
2. 主隔離區(qū)
flutter應(yīng)用會(huì)創(chuàng)建一個(gè)默認(rèn)的隔離區(qū)檐什,可以使用下面的代碼訪問
Isolate.current
3. 隔離區(qū)之間的通信
使用ReceivePort實(shí)現(xiàn)不同隔離區(qū)的通信
import 'dart:isolate';
late Isolate isolate;
void sendMsg(SendPort sendPort) {
sendPort.send('hellow wrold');
}
main() async {
final receivePort = ReceivePort();
isolate = await Isolate.spawn(sendMsg, receivePort.sendPort);
receivePort.listen((message) {
print(message);
});
}
4. 銷毀隔離區(qū)
銷毀隔離區(qū)的代碼如下
isolate.kill();
5. MicroTask queue
如上圖所示碴卧,我們的flutter應(yīng)用在創(chuàng)建后不僅有 event quene,還存在著 microTask queue(微任務(wù)隊(duì)列)乃正,而且在事件循環(huán)中住册,microTask queue事件處理的優(yōu)先級(jí)其實(shí)是比event quene事件更高的,也就是說microTask queue中的事件會(huì)比event quene的事件更快被處理瓮具,event quene的事件必須等到microTask queue的事件被清空了才能被處理荧飞,所以說凡人,一般情況下,我們是不會(huì)用到MicroTask queue的垢箕,但是一旦用到了划栓,我們不能在microTask queue中做特別耗時(shí)的任務(wù),否則會(huì)導(dǎo)致event quene的事件無法被處理条获,而widget的構(gòu)建和刷新是event quene的事件,這樣就會(huì)導(dǎo)致UI卡頓掉幀
6.最終思考與總結(jié)
1. 思考
用Future做網(wǎng)絡(luò)請(qǐng)求不會(huì)導(dǎo)致卡頓蒋歌,這是為什么
既然Future對(duì)于event loops來說只是一個(gè)事件帅掘,使用Future做耗時(shí)任務(wù)會(huì)導(dǎo)致UI卡頓,那為什么我們使用Future做網(wǎng)絡(luò)請(qǐng)求(時(shí)長(zhǎng)可能有十幾秒)的時(shí)候不會(huì)導(dǎo)致掉幀或者卡頓呢堂油?這是因?yàn)榫W(wǎng)絡(luò)請(qǐng)求(http.get())就不是在dart層完成的修档,它其實(shí)是由dart層告訴操作系統(tǒng),操作系統(tǒng)開啟了一個(gè)異步任務(wù)完成的府框,操作系統(tǒng)做完網(wǎng)絡(luò)請(qǐng)求后將數(shù)據(jù)再告訴dart層吱窝,dart層再完成一個(gè)簡(jiǎn)單的讀操作,這就是為什么一個(gè)網(wǎng)絡(luò)請(qǐng)求可以長(zhǎng)達(dá)十幾秒又不卡UI的原因迫靖,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的
知識(shí)儲(chǔ)備(external關(guān)鍵字)
external是flutter提供的一個(gè)關(guān)鍵字院峡,可以在非抽象類中實(shí)現(xiàn)類似抽象方法的方法,將方法的聲明和實(shí)現(xiàn)相分離系宜,
external的作用如下:
可以在非抽象類中實(shí)現(xiàn)類似抽象方法的方法照激,將方法的聲明和實(shí)現(xiàn)相分離,這樣可以實(shí)現(xiàn)在不同的平臺(tái)盹牧,不管是dart for vm, 還是dart for web, 上層應(yīng)用都可以只使用一套api俩垃,然后由不同的平臺(tái)各種對(duì)external 修飾的方法添加實(shí)現(xiàn),有助于提升擴(kuò)展性汰寓,減低應(yīng)用實(shí)現(xiàn)跨平臺(tái)的難度
external 聲明的方法由底層sdk根據(jù)不同的平臺(tái)(vm或者web)添加實(shí)現(xiàn)口柳,方法所在類不用聲明為抽象類,所以可以直接實(shí)例化
源碼解析
我們查看Dio源碼有滑,發(fā)現(xiàn)使用Dio做網(wǎng)絡(luò)請(qǐng)求跃闹,其實(shí)網(wǎng)絡(luò)請(qǐng)求會(huì)交給HttpClient,由HttpClient的openUrl方法開啟網(wǎng)絡(luò)請(qǐng)求俺孙,而openUrl其實(shí)會(huì)調(diào)用到一個(gè)external方法辣卒,代碼如下
我們?cè)趂lutter3.3.2\flutter\bin\cache\dart-sdk\lib_internal\vm\socket_patch.dart找到了對(duì)應(yīng)的實(shí)現(xiàn),代碼如下
我們繼續(xù)查看這個(gè)_startConnect方法的調(diào)用過程睛榄,發(fā)現(xiàn)最終會(huì)調(diào)用到另一個(gè)external方法荣茫,代碼如下
nativeCreateUnixDomainConnect也是一個(gè)external方法,我們暫時(shí)沒有找到它的實(shí)現(xiàn)场靴,但是我們其實(shí)可以知道Unix domain socket是一種終端啡莉,可以使同一臺(tái)操作系統(tǒng)上的兩個(gè)或多個(gè)進(jìn)程進(jìn)行數(shù)據(jù)通信港准,然后我們?cè)倏纯磏ativeCreateUnixDomainConnect方法所在類的一些注釋,會(huì)發(fā)現(xiàn)一些關(guān)鍵信息咧欣,代碼如下
畫了紅圈是關(guān)鍵的一句注釋浅缸,這句注釋的意思就是說_NativeSocket封裝了一個(gè)操作系統(tǒng)的socket,os是操作系統(tǒng)的意思魄咕,也就是說調(diào)用socket.nativeCreateUnixDomainConnect方法的時(shí)候會(huì)到調(diào)用操作系統(tǒng)的socket衩椒,也就是說網(wǎng)絡(luò)請(qǐng)求其實(shí)是操作系統(tǒng)完成的,這就是為什么flutter應(yīng)用是單線程模型的應(yīng)用哮兰,但是在默認(rèn)的isolate做網(wǎng)絡(luò)請(qǐng)求卻不會(huì)卡 UI 的原因毛萌,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的,是操作系統(tǒng)完成的
2. 總結(jié)
我們了解了dart的單線程模型和事件機(jī)制喝滞,知道了widget的構(gòu)建和刷新是在事件循環(huán)的條件下完成的阁将,這使我們知道UI掉幀的本質(zhì)原因(event queue 的UI刷新事件沒有及時(shí)被處理),也知道了新建future和microTask 無法解決UI卡頓的問題右遭,網(wǎng)絡(luò)請(qǐng)求不會(huì)卡UI是因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的做盅,是操作系統(tǒng)完成的,還知道了如何新建隔離區(qū)窘哈,這可以為我們?nèi)蘸笞鯱I流暢度優(yōu)化提供更多思路吹榴,帶領(lǐng)我們走向正確的優(yōu)化方向