原本 Flutter 寫起來很開心潮针,任何耗時較長的工作赖淤,就放到一個
Future<T> someFunction(…) async {…}
中就好缸匪,不需要像 Android 里面還需要 rxJava 或是自己建 thread(線程) 來處理。
Ios中一樣是要用 thread(線程) 來處理
但是后來發(fā)現(xiàn)其實(shí)沒有那么單純。就像下面這篇文章的標(biāo)題一樣:剛接觸 Flutter 的人通常不會管到 asynchronous 的問題儡蔓,直到 UI 開始變得很卡的時候。
isolate 是啥
我們都知道 dart 是單線程異步編程模型 這一點(diǎn) 和js 很像
我們首先介紹一下 isolate 是什么東西 雖然 dart 是單線程異步模型 但是 dart 是支持(多線程)的在 dart 中 有一個 模塊 就是 isolate (隔離) 我們的 程序 就是 運(yùn)行 在 main isolate 中 而且 這個 isolate (隔離) 和我們 了解的其他語言的 線程 還不一樣 它是不共享內(nèi)存的 而且 在 flutter 中所有的 channel 和原生通信部分的 引用都是在主線程注冊的 也就是 main isolate 才能調(diào)用到 (需要注意 在 非 main isolate 中無法調(diào)用 channel 通信方法 ) isolate 和 isolate 之間的 通信 是通過 ReceivePort 這個類
那么有啥問題
在 Dart 中 async 和 Future 無法解決所有耗時的工作疼邀。Dart 雖然支持 非同步執(zhí)行,但其實(shí)如果是透過 async keyword 的話召锈,只是把工作丟到同一個 event loop 中旁振, 讓他暫時不會卡住目前的工作 , 等到真的輪到它執(zhí)行的時候 涨岁,如果它真的很耗時拐袜,那 main isolate 還是會 freeze(凍結(jié)) 住的 (為什么會凍結(jié)? 主線程負(fù)責(zé) UI的渲染 工作 但是 如果 密集型計(jì)算 很耗時 假如 這個計(jì)算 占用 1s的時間 你的UI就會卡住1s) 梢薪。Dart 主要的 task 都是在 main isolate 中完成的蹬铺,isolate 像是個 single thread 的 process。如果真的想要讓某些工作能夠同時執(zhí)行秉撇,不要卡住 main isolate 的話甜攀,就得要自己產(chǎn)生新的 isolate 來執(zhí)行。但 isolate 又不是那么好寫琐馆,必須由ReceivePort來傳輸參數(shù)(線程之間的交互通訊)规阀。下面有個小示例:
Isolate isolate;
void startRealAsyncTask() async {
// need a ReceivePort to receive messages.
ReceivePort receivePort= ReceivePort();
isolate = await Isolate.spawn(heavyTask, receivePort.sendPort);
receivePort.listen((data) {
stdout.write('RECEIVE: ' + data + ', ');
});
}
void heavyTask(SendPort sendPort) {
// doing something very heavy here.
String msg = "I'm done";
sendPort.send(msg);
}
void stop() {
if (isolate != null) {
stdout.writeln('killing isolate');
isolate.kill(priority: Isolate.immediate);
isolate = null;
}
}
void main() async {
stdout.writeln('spawning isolate...');
await startRealAsyncTask();
stdout.writeln('press enter key to quit...');
await stdin.first;
stop();
stdout.writeln('goodbye!');
exit(0);
}
好在針對一般需要比較多時間執(zhí)行的工作,Dart 提供了一個比較容易使用的 compute() function瘦麸,幫助開發(fā)者包裝自建 isolate 的繁雜流暢谁撼。
以下是個簡單的范例。原先第一行的 processImage() 因?yàn)樾枰槍D片的每個 pixel 做處理滋饲,所以會很花時間厉碟,如果只是單純用 async 的話,在執(zhí)行的時候依然會在 main isolate 做屠缭,造成 畫面(UI刷新)反應(yīng)很不流暢箍鼓。將它改成用 compute() 來調(diào)用后,Dart 會幫忙產(chǎn)生新的 isolate 同步執(zhí)行勿她。如此一來畫面就不會再卡卡的了袄秩。
static Image processImage(Image srcImage) {
return srcImage.replaceColor(Colors.white, Colors.transparent);
}
Image _getTransparentBackgroundImage(Image srcImage) async {
Image resultImage = await compute(processImage, srcImage);
return resultImae;
}
將現(xiàn)有的 async function 改成 compute()調(diào)用,只是幾行 code 的事逢并,就可以解決在同一個 event loop 中 執(zhí)行影響其他 task 導(dǎo)致 不流暢 卡頓的問題之剧,但如同上面的例子所示, compute 呼叫的 function 必須是要是top-level function (全局函數(shù))或是 static (靜態(tài)方法)才行砍聊,而且 調(diào)用的方法本身 是 不支持 async function 的 已經(jīng) 大佬 提供 實(shí)現(xiàn)方案 但是 在 目前flutter的版本 中 并沒有合并 代碼 當(dāng)前 是 1.7版本 ** 實(shí)現(xiàn)的 pull地址 **
請求合并的 核心 代碼 如下
測試用例
由于還沒有合并到當(dāng)前flutter 版本中 不建議直接修改flutter 源碼雖然(可以 直接改) 所以我自己對 isolate ReceivePort 做了 簡單的封裝
實(shí)現(xiàn)線程管理器
import 'dart:isolate';
typedef LikeCallback = void Function(Object value);
class ThreadManagement {
//entryPoint 必須是靜態(tài)方法
static Future<Map> runtask (void entryPoint(SendPort message), LikeCallback(Object value),{Object parameter})async{
final response = ReceivePort();
Isolate d = await Isolate.spawn(entryPoint, response.sendPort);
// 調(diào)用sendReceive自定義方法
if(parameter!=null){
SendPort sendPort = await response.first;
ReceivePort receivePort = ReceivePort();
sendPort.send([parameter, receivePort.sendPort]);
receivePort.listen((value){
receivePort.close();
d.kill();
LikeCallback(value);
});
return {
'isolate': d,
"receivePort":receivePort,
};
}else{
response.listen((value){
response.close();
d.kill();
LikeCallback(value);
});
return {
'isolate': d,
"receivePort":response,
};
}
}
}
編寫 任務(wù)函數(shù)(僅做參考)
static void getbannerthread(SendPort port) async {
var c = await HttpManager.isolationnetFetch(
"http://118.25.61.120/api/v2/banner"
,null,
null, new Options(method: 'GET'));
port.send(c);
}
port.send(c); 是回調(diào)計(jì)算結(jié)果
調(diào)用任務(wù)
ThreadManagement.runtask(API.getbannerthread, (value){
if(value != null){
//業(yè)務(wù)邏輯
}
});
帶參數(shù)任務(wù)
static getVideolisttask(SendPort port) async {
ReceivePort receivePort =ReceivePort();
port.send(receivePort.sendPort);
// 監(jiān)聽外界調(diào)用
await for (var msg in receivePort) {
Map requestURL =msg[0];
SendPort callbackPort =msg[1];
receivePort.close();
var res = await HttpManager.isolationnetFetch(
"http://xxxxxx?type="+requestURL["type"]+"&after="+requestURL["after"]
,null,
null, new Options(method: 'GET'));
callbackPort.send(res);
}
}
執(zhí)行帶參數(shù)的任務(wù)
ThreadManagement.runtask(API.getVideolisttask, (value){
if(value != null){
//業(yè)務(wù)邏輯
}
},parameter: {
"type":"hot",
"after":"1"
});
轉(zhuǎn)載:姜姜和張張
原文地址
著作權(quán)歸作者所有背稼。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處玻蝌。