這篇文章將會講解flutter中的Isolate,這有助于幫你解決某些耗時計算問題導致的卡頓。
一 . 原始代碼
為什么要Isolate,我們先看一段比較簡單的代碼:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class TestWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return TestWidgetState();
}
}
class TestWidgetState extends State<TestWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
children: <Widget>[
Container(
width: 100,
height: 100,
child: CircularProgressIndicator(),
),
FlatButton(
onPressed: () async {
_count = countEven(1000000000);
setState(() {});
},
child: Text(
_count.toString(),
)),
],
mainAxisSize: MainAxisSize.min,
),
),
);
}
//計算偶數(shù)的個數(shù)
static int countEven(int num) {
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}
}
UI包含兩個部分,一個不斷轉圈的progress指示器,一個按鈕,當點擊按鈕的時候,找出比某個正整數(shù)n小的數(shù)的偶數(shù)的個數(shù)(請忽視具體算法,故意做耗時計算用,哈哈)。我們來運行一下代碼看看效果:

可以看到,本來是很流暢的轉圈,當我點擊按鈕計算的時候,UI出現(xiàn)了卡頓,為什么會出現(xiàn)卡頓,因為我們的計算默認是在UI線程中的,當我們調用countEven的時候,這個計算需要耗時,而在這期間,UI是沒有機會去調用刷新的,因此會卡頓,計算完成后,UI恢復正常刷新。
二. 使用async優(yōu)化
那么有些同學就會說了,在dart中,有async關鍵字,我們可以用異步計算,這樣就不會影響UI的刷新了,事實真的是這樣嗎?我們一起來修改一下代碼:
a. 將count改為asyncCountEven
static Future<int> asyncCountEven(int num) async{
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}
b. 調用:
_count = await asyncCountEven(1000000000);
我們繼續(xù)運行一下代碼,看現(xiàn)象:

仍然卡頓,說明異步是解決不了問題的,為什么?因為我們仍舊是在同一個UI線程中做運算,異步只是說我可以先運行其他的,等我這邊有結果再返回,但是,記住,我們的計算仍舊是在這個UI線程,仍會阻塞UI的刷新,異步只是在同一個線程的并發(fā)操作。
三. 使用compute優(yōu)化
那么我們怎么解決這個問題呢,其實很簡單,我們知道卡頓的原因是在同一個線程中導致的,那我們有沒有辦法將計算移到新的線程中呢,當然是可以的。不過在dart中,這里不是稱呼線程,是Isolate,直譯叫做隔離,這么古怪的名字,是因為隔離不共享數(shù)據,每個隔離中的變量都是不同的,不能相互共享。
但是由于dart中的Isolate比較重量級,UI線程和Isolate中的數(shù)據的傳輸比較復雜,因此flutter為了簡化用戶代碼,在foundation庫中封裝了一個輕量級compute操作,我們先看看compute,然后再來看Isolate。
要使用compute,必須注意的有兩點,一是我們的compute中運行的函數(shù),必須是頂級函數(shù)或者是static函數(shù),二是compute傳參,只能傳遞一個參數(shù),返回值也只有一個,我們先看看本例中的compute優(yōu)化吧:
真的很簡單,只用在使用的時候,放到compute函數(shù)中就行了。
_count = await compute(countEven, 1000000000);
再次運行,我們來看看效果吧:

可以看到,現(xiàn)在的計算并不會導致UI卡頓,完美解決問題。
四. 使用Isolate優(yōu)化
但是,compute的使用還是有些限制,它沒有辦法多次返回結果,也沒有辦法持續(xù)性的傳值計算,每次調用,相當于新建一個隔離,如果調用過多的話反而會適得其反。在某些業(yè)務下,我們可以使用compute,但是在另外一些業(yè)務下,我們只能使用dart提供的Isolate了,我們先看看Isolate在本例中的使用:
a. 增加這兩個函數(shù)
static Future<dynamic> isolateCountEven(int num) async {
final response = ReceivePort();
await Isolate.spawn(countEvent2, response.sendPort);
final sendPort = await response.first;
final answer = ReceivePort();
sendPort.send([answer.sendPort, num]);
return answer.first;
}
static void countEvent2(SendPort port) {
final rPort = ReceivePort();
port.send(rPort.sendPort);
rPort.listen((message) {
final send = message[0] as SendPort;
final n = message[1] as int;
send.send(countEven(n));
});
}
b. 使用
_count = await isolateCountEven(1000000000);
相對于compute復雜了很多,效果就不貼了,和compute一樣,毫無卡頓。。
五. 擴展
isolate使用這么復雜,那么我們在那些情況下使用它呢?
想象一下,假如你有一個業(yè)務,是使用socket和服務器連接,不斷的從服務器中讀取tcp流,如果業(yè)務需要拆分包的話,我們需要不斷的將讀取到的tcp流進行拆包分包,然后使用獲取的數(shù)據來更新UI。首先,我們的socket與服務器通信,如果服務器推送消息過快,那么肯定會出現(xiàn)上面的情況,socket這邊一直阻塞線程,導致UI刷新卡頓,所以我們需要將socket這邊的業(yè)務放到隔離里面,但是,compute函數(shù)的限制我們也說了,它基本是是一次運行一次返回,但是業(yè)務要求是通過tcp的長連接不斷獲取服務器的數(shù)據,所以,這里我們就只能使用isolate,在UI和isolate里面使用ReceivePort進行雙向通信,這樣才能保證UI不卡頓的情況下仍然保持業(yè)務的完整性。
————————————————
版權聲明:本文為CSDN博主「Hirabbit_jaden」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/email_jade/article/details/88941434