我們小學二年級就學過:
Dart 是單線程的并且 Flutter 依賴于 Dart赃梧。
但是如果我們要在主線程做一些計算量大的操作,就必然會阻塞線程,使UI更新卡頓甚至卡死。那怎么辦呢肉康?
好消息是 Dart 為我們提供了 isolate,isolate 跟線程差不多灼舍,他是 Dart 中的線程吼和。
isolate 與線程的區(qū)別就是線程與線程之間是共享內(nèi)存的,而 isolate 和 isolate 之間是不共享的骑素,所以叫 isolate (隔離)炫乓。
在flutter 里面主線程就是主 isolate 。如果我們要進行一些大計算量的操作就應該啟動一個新的 isolate砂豌。
那么應該如何來開啟呢厢岂?在此之前我想講個故事光督。
小紅與小藍的故事
有個舞者叫小紅阳距,她正在給觀眾跳舞。
但是觀眾卻要求她一邊跳舞一邊計算一個數(shù)字里面有多少個偶數(shù)结借。于是筐摘。。。
這那行翱臁圃酵!你必須給我一邊跳一邊算,算的時候不能停下來馍管!
于是小紅沒辦法郭赐,決定在異世界召喚一個小藍來幫她計算。
但是小紅和小藍被異世界的屏障隔離确沸,她們也沒有思想共通的超能力捌锭。只能在召喚的同時傳送一個包裹給小藍。
小藍被召喚出來后收到包裹罗捎,打開后里面是要計算的數(shù)字观谦,就開始計算,但是計算后要怎么把結果告訴小紅呢桨菜?
上帝做了一個約定豁状,在小紅召喚小藍的時候,會變一個傳送裝置(傳送裝置可以用來接收包裹倒得,還可以生成一個專屬發(fā)送器)泻红。然后把發(fā)送器傳送給小藍。
當小藍被召喚出來后屎暇,打開包裹承桥,里面是一個發(fā)送器,然后小藍自己也變一個傳送裝置根悼,生成一個發(fā)送器凶异,然后用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送給小紅。發(fā)送出去后就坐在傳送裝置旁邊等包裹挤巡。
當小紅收到小藍的發(fā)送器后就把小藍的發(fā)送器存起來剩彬。
當有觀眾要求小紅計算時,就分神一邊跳舞矿卑,一邊生成一個臨時傳送裝置喉恋,把要計算的數(shù)字和臨時發(fā)送器打包成一個包裹,然后通過小藍的發(fā)送器發(fā)給小藍母廷,等傳送裝置出結果轻黑。因為不用自己算了,只是等琴昆,所以跳舞的時候線條也流暢了氓鄙,動作也優(yōu)美了。
說回小藍這邊业舍,小藍看到傳送裝置出現(xiàn)了一個包裹抖拦,里面是一個臨時發(fā)送器升酣,還有一個數(shù)字。于是小藍就開始計算态罪。算好了就用臨時發(fā)送器把數(shù)字發(fā)送給小紅噩茄。
小紅收到結果后就告訴觀眾,那個數(shù)字有多少個偶數(shù)复颈。
故事結束绩聘,第一次嘗試這樣的風格,可能寫得有點爛耗啦,不過結合代碼來看的話君纫,應該還是挺容易理解的。
代碼實踐
首先我們先讓小紅跳起舞來芹彬。
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小紅',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
],
),
),
);
}
接下來讓小紅計算一個數(shù)字里面有多少個偶數(shù)蓄髓。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小紅',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
Padding(
padding: EdgeInsets.only(top: 16),
child:
RaisedButton(onPressed: count, child: Text('異步計算偶數(shù)的個數(shù)')),
),
Text(result)
],
),
),
);
}
int getRandom() {
int a = Random().nextInt(100);
return a + 1000000000;
}
// 異步計算
count() async {
int random = getRandom();
int r = countEven(random);
setState(() {
this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
});
}
//計算偶數(shù)的個數(shù)
int countEven(num num) {
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}
這就是效果
定義 isolate
我愿稱之為召喚小藍。
首先我們要知道兩個類:
ReceivePort
SendPort
ReceivePort 就是故事中的傳送裝置舒帮,而 SendPort 則是發(fā)送器会喝。
我們可以通過以下方式創(chuàng)建傳送裝置和對應的發(fā)送器
ReceivePort receive = ReceivePort();
SendPort sender = receive.sendPort;
好的,知道這些就行了玩郊。接下來我們定義小藍肢执。
// 消息包裹,用來存臨時發(fā)送器和消息
class MessagePackage {
SendPort sender; // 臨時發(fā)送器
dynamic msg; // 消息
MessagePackage(this.sender, this.msg);
}
// 我是小藍译红,負責計算偶數(shù)的個數(shù),我必須是頂級函數(shù)
blueCounter(SendPort redSendPort) {
// 創(chuàng)建小藍的傳送裝置
ReceivePort blueReceivePort = ReceivePort();
// 用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送出去
redSendPort.send(blueReceivePort.sendPort);
// 監(jiān)聽小藍的傳送裝置预茄,等待小紅叫小藍計算
blueReceivePort.listen((package) {
// 這里的msg是dynamic,需要轉換成 MessagePackage 類侦厚,上面自己定義的包裹封裝類
MessagePackage _msg = package as MessagePackage;
// 小藍開始計算
int r = countEven(_msg.msg as num);
// 計算好了用小紅的臨時發(fā)送器告訴小紅
_msg.sender.send(r);
});
}
創(chuàng)建isolate
工具人小藍定義好了耻陕,我們?nèi)コ跏蓟?召喚)一下小藍。
// 創(chuàng)建isolate
void createIsolate() async {
// 創(chuàng)建小紅的接收器刨沦,用來接收小藍的發(fā)送器
ReceivePort redReceive = ReceivePort();
// 創(chuàng)建 isolate诗宣, 并且把小紅的發(fā)送器傳給小藍
isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
// 等待小藍把發(fā)送器發(fā)送給小紅
blueSender = await redReceive.first;
// 不用了記得關閉接收器
redReceive.close();
}
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
// 在initState中初始化isolate
createIsolate();
super.initState();
}
現(xiàn)在小藍已經(jīng)被召喚了出來,并且和小紅建立了通信想诅。
使isolate 開始計算
接下來我們就讓小紅開始計算吧召庞。
@override
Widget build(BuildContext context) {
...
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: isolateCount, child: Text('isolate計算偶數(shù)的個數(shù)')
),
),
...
}
// 開啟isolate計算
isolateCount() async {
// 獲取要計算的數(shù)字
int random = getRandom();
// 創(chuàng)建一個臨時傳送裝置
ReceivePort _temp = ReceivePort();
// 用小藍的發(fā)送裝置發(fā)送一個消息包裹,里面是臨時傳送裝置的發(fā)送器和要計算的數(shù)字
blueSender.send(MessagePackage(_temp.sendPort, random));
// 等待臨時傳送裝置返回計算結果
int r = await _temp.first;
// 不用了記得關閉臨時接收器
_temp.close();
// 把計算結果告訴觀眾
setState(() {
this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
});
}
需要注意的是當使用完了 isolate 記得要銷毀来破。
@override
void dispose() {
// 銷毀 isolate
isolate?.kill(priority: Isolate.immediate);
super.dispose();
}
OK,到這里相信你已經(jīng)看懂并會使用 isolate 了篮灼。
我們來看看效果圖。
使用 computed
到這里還沒完徘禁,也許你會覺得太麻煩了诅诱。是的這樣用 isolate 太麻煩了,isolate 被設計成可以多次輸入輸出晌坤,而我們做這個計算只有一次輸入和輸出逢艘,那么我們就可以用 flutter 為我們提供的 computed 來完成計算操作,它是對 isolate 的一個封裝骤菠。下面看看怎么用吧它改!敲簡單的。
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'isolate Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'isolate Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
String result = '';
SendPort blueSender;
Isolate isolate;
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
// 在initState中初始化isolate
createIsolate();
super.initState();
}
@override
void dispose() {
// 銷毀 isolate
isolate?.kill(priority: Isolate.immediate);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小紅',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(onPressed: count, child: Text('異步計算偶數(shù)的個數(shù)')),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: isolateCount, child: Text('isolate計算偶數(shù)的個數(shù)')),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: computeCount, child: Text('compute計算偶數(shù)的個數(shù)')),
),
Text(result)
],
),
),
);
}
// 獲取隨機數(shù)
int getRandom() {
int a = Random().nextInt(100);
return a + 1000000000;
}
// 異步計算
count() async {
int random = getRandom();
int r = countEven(random);
setState(() {
this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
});
}
// 創(chuàng)建isolate
void createIsolate() async {
// 創(chuàng)建小紅的接收器商乎,用來接收小藍的發(fā)送器
ReceivePort redReceive = ReceivePort();
// 創(chuàng)建 isolate央拖, 并且把小紅的發(fā)送器傳給小藍
isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
// 等待小藍把發(fā)送器發(fā)送給小紅
blueSender = await redReceive.first;
// 不用了記得關閉接收器
redReceive.close();
}
// 利用compute計算
computeCount() async {
int random = getRandom();
// compute 的回調(diào)函數(shù)必須是頂級函數(shù)或者static函數(shù)
int r = await compute(countEven, random);
setState(() {
this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
});
}
// 開啟isolate計算
isolateCount() async {
// 獲取要計算的數(shù)字
int random = getRandom();
// 創(chuàng)建一個臨時傳送裝置
ReceivePort _temp = ReceivePort();
// 用小藍的發(fā)送裝置發(fā)送一個消息包裹,里面是臨時傳送裝置的發(fā)送器和要計算的數(shù)字
blueSender.send(MessagePackage(_temp.sendPort, random));
// 等待臨時傳送裝置返回計算結果
int r = await _temp.first;
_temp.close();
// 把計算結果告訴觀眾
setState(() {
this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
});
}
}
// 消息包裹鹉戚,用來存臨時發(fā)送器和消息
class MessagePackage {
SendPort sender; // 臨時發(fā)送器
dynamic msg; // 消息
MessagePackage(this.sender, this.msg);
}
// 我是小藍鲜戒,負責計算偶數(shù)的個數(shù),我必須是頂級函數(shù)
blueCounter(SendPort redSendPort) {
// 創(chuàng)建小藍的傳送裝置
ReceivePort blueReceivePort = ReceivePort();
// 用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送出去
redSendPort.send(blueReceivePort.sendPort);
// 監(jiān)聽小藍的傳送裝置,等待小紅叫小藍計算
blueReceivePort.listen((package) {
// 這里的msg是dynamic抹凳,需要轉換成 MessagePackage 類遏餐,上面自己定義的包裹封裝類
MessagePackage _msg = package as MessagePackage;
// 小藍開始計算
int r = countEven(_msg.msg as num);
// 計算好了用小紅的臨時發(fā)送器告訴小紅
_msg.sender.send(r);
});
}
//計算偶數(shù)的個數(shù),此函數(shù)需要大量的計算資源和時間
int countEven(num num) {
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}