当执行完main()
入口函数后,Flutter会创建一个Main Isolate。一般情况下任务都是在这个Main Isolate中执行的。
多线程
一般情况下在Main Isolate执行任务是可以接受的,但是把一些耗时操作放在Main Isolate中执行,会造成掉帧的现象,这会对用户体验造成严重影响。此时,选择将耗时任务分发到其他的Isolate中就是一个很好的实现方式了。
所有的Dart Code都是在Isolate
中执行的,代码只能使用同一个Isolate
中的内容,不同的 Isolate
是内存隔离的,因此只能通过 Port 机制发送消息通信,其原理是向不同的 Isolate 队列中执行写任务。
案例
我们做了个简单的Demo,屏幕中间有一个心在不停的动画(由小变大,再由大变小)。当我们点击右下角对的加号按钮,会进行一个耗时的运算。如果耗时操作在Main Isolate执行,将会造成界面的丢帧,动画将会出现卡顿的情况。
我们目前就是需要解决这个掉帧的问题。
1.compute
方法
Flutter封装了一个compute
这个高级API函数可以让我们方便的实现多线程的功能。
Future compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
}
compute
接收两个必传参数:1,需要执行的方法;2,传入的参数,这参数最多只能是1个,所以多个参数需要封装到Map
中;
- 最开始的代码
// 耗时操作的方法:bigCompute
Future bigCompute(int initalNumber) async {
int total = initalNumber;
for (var i = 0; i < 1000000000; i++) {
total += i;
}
return total;
}
// 点击按钮调用的方法:calculator
void calculator() async {
int result = await bigCompute(0);
print(result);
}
// FloatingActionButton的点击事件
FloatingActionButton(
onPressed: calculator,
tooltip: ‘Increment’,
child: Icon(Icons.add),
)
- 修改代码
- 新建一个
calculatorByComputeFunction
方法,用compute
调用bigCompute
方法:
void calculatorByComputeFunction() async {
// 使用compute
调用bigCompute
方法,传参0
int result = await compute(bigCompute, 0);
print(result);
}
- 修改FloatingActionButton的点击事件方法为
calculatorByComputeFunction
FloatingActionButton(
onPressed: calculatorByComputeFunction,
tooltip: ‘Increment’,
child: Icon(Icons.add),
)
咱点击试试?
[VERBOSE-2:ui_dart_state.cc(186)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function ‘bigCompute’:.)
- 解决Error:将
bigCompute
改为为static方法(改为全局函数也是可行的)
static Future bigCompute(int initalNumber) async {
int total = initalNumber;
for (var i = 0; i < 1000000000; i++) {
total += i;
}
return total;
}
警告:还有一个需要注意的是所有的
Platform-Channel
的通信必须在Main Isolate中执行,譬如在其他Isolate中调用rootBundle.loadString("assets/***")
就掉坑里了。
2. 直接使用Isolate
上面我们用compute
方法,基本上没有看到Isolate
的身影,因为Flutter帮我们做了很多工作,包括Isolate
创建,销毁,方法的执行等等。一般情况下我们使用这个方法就够了。
但是这个方法有个缺陷,我们只能执行一个任务,当我们有多个类似的耗时操作时候,如果使用这个compute
方法将会出现大量的创建和销毁,是一个高消耗的过程,如果能复用Isolate
那就是最好的实现方式了。
多线程Isolate
间通信的原理如下:
-
当前
Isolate
接收其他Isolate
消息的实现逻辑:Isolate
之间是通过Port进行通信的,ReceivePort
是接收器,它配套有一个SendPort
发送器, 当前Isolate
可以把SendPort
发送器送给其他Isolate
,其他Isolate
通过这个SendPort
发送器就可以发送消息给当前Isolate
了。 -
当前
Isolate
给其他Isolate
发消息的实现逻辑: 其他Isolate
通过当前Isolate
的SendPort
发送器发送一个SendPort2
发送器2过来,其他的Isolate
则持有SendPort 2
发送器2对应的接收器ReceivePort2
接收器2,当前Isolate
通过SendPort 2
发送消息就可以被其他Isolate
收到了。
是不是很绕!我再打个比喻:市面上有一套通信工具套件,这套通信工具套件包括一个接电话的工具和一个打电话的工具。A留有接电话的,把打电话的送给B,这样B就可以随时随地给A打电话了(此时是单向通信)。 如果B也有一套工具,把打电话的送给A,这样A也能随时随地给B打电话了(此时是双向通信了)。
上代码:
class _MyHomePageState extends State
with SingleTickerProviderStateMixin {
// 1.1 新建的isolate
Isolate isolate;
// 1.2 Main Isolate的接收器
ReceivePort mainIsolaiteReceivePort;
// 1.3 Other Isolate的发送器
SendPort otherIsolateSendPort;
// 新建(复用)Isolate
void spawnNewIsolate() async {
// 2.1 建一个接收Main Isolate的接收器
if (mainIsolaiteReceivePort == null) {
mainIsolaiteReceivePort = ReceivePort();
}
try {
if (isolate == null) {
// 2.2 新建的isolate, 把Main Isolate发送器传给新的isolate,calculatorByIsolate是需要执行的任务
isolate = await Isolate.spawn(
calculatorByIsolate, mainIsolaiteReceivePort.sendPort);
// 2.3 Main Isolate 通过接收器接收新建的isolate发来的消息
mainIsolaiteReceivePort.listen((dynamic message) {
if (message is SendPort) {
// 2.4 如果新建的isolate发来的是一个发送器,就通过这个发送器给新建的isolate发送值过去(此时双向通讯建立成功)
otherIsolateSendPort = message;
otherIsolateSendPort.send(1);
print(“双向通讯建立成功,主isolate传递初始参数1”);
} else {
// 2.5 如果新建的isolate发来了一个值,我们知道是耗时操作的计算结果。
print(“新建的isolate计算得到的结果$message”);
}
});
} else {
// 2.6 复用otherIsolateSendPort
if (otherIsolateSendPort != null) {
otherIsolateSendPort.send(1);
print(“双向通讯复用,主isolate传递初始参数1”);
}
}
} catch (e) {}
}
// 这个是新的Isolate中执行的任务
static void calculatorByIsolate(SendPort sendPort) {
// 3.1 新的Isolate把发送器发给Main Isolate
ReceivePort receivePort = new ReceivePort();
sendPort.send(receivePort.sendPort);
// 3.2 如过Main Isolate发过来了初始数据,就可以进行耗时计算了
receivePort.listen((val) {
print(“从主isolate传递过来的初始参数是$val”);
int total = val;
for (var i = 0; i < 1000000000; i++) {
total += i;
}
// 3.3 通过Main Isolate的发送器发给Main Isolate计算结果
sendPort.send(total);
});
}
@override
void dispose() {
// 释放资源
mainIsolaiteReceivePort.close();
isolate.kill();
super.dispose();
}
}
代码注释的很详细了,就不再解释了。是不是代码好多的感觉,其实如果理解流程了逻辑倒不复杂。
关于Isolate
的概念和使用我们就介绍到这里,接下来我们来介绍Isolate
中的一个重要知识点Event Loop
.
Event Loop
Loop
这个概念绝大部分开发者都应该很熟悉了,iOS中有NSRunLoop
,Android中有Looper
, js中有Event Loop
,名字上类似,其实所做的事情也是类似的。
Event Loop的官方介绍如下图:
- 静态示意图
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
总结
可以看出,笔者的工作学习模式便是由以下 「六个要点」 组成:
❝ 多层次的工作/学习计划 + 番茄工作法 + 定额工作法 + 批处理 + 多任务并行 + 图层工作法❞
希望大家能将这些要点融入自己的工作学习当中,我相信一定会工作与学习地更富有成效。
下面是我学习用到的一些书籍学习导图,以及系统的学习资料。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。
**如:我需要学习 **Flutter的知识。(大家可以参考我的学习方法)
- Flutter 的思维导图(无论学习什么,有学习路线都会事半功倍)
- Flutter进阶学习全套手册
- Flutter进阶学习全套视频
大概就上面这几个步骤,这样学习不仅高效,而且能系统的学习新的知识。
番茄工作法 + 定额工作法 + 批处理 + 多任务并行 + 图层工作法❞
希望大家能将这些要点融入自己的工作学习当中,我相信一定会工作与学习地更富有成效。
下面是我学习用到的一些书籍学习导图,以及系统的学习资料。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。
**如:我需要学习 **Flutter的知识。(大家可以参考我的学习方法)
- Flutter 的思维导图(无论学习什么,有学习路线都会事半功倍)
[外链图片转存中…(img-ePWqDBTp-1711566392829)]
- Flutter进阶学习全套手册
[外链图片转存中…(img-78bD7IMK-1711566392829)]
- Flutter进阶学习全套视频
[外链图片转存中…(img-vKJTthq5-1711566392829)]
大概就上面这几个步骤,这样学习不仅高效,而且能系统的学习新的知识。