Flutter异步编程详解

// 耗时操作的方法: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),

)

  • 修改代码
  1. 新建一个calculatorByComputeFunction方法,用compute调用bigCompute方法:

void calculatorByComputeFunction() async {

// 使用compute调用bigCompute方法,传参0

int result = await compute(bigCompute, 0);

print(result);

}

  1. 修改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’:.)

  1. 解决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间通信的原理如下:

  1. 当前Isolate接收其他Isolate消息的实现逻辑: Isolate之间是通过Port进行通信的,ReceivePort是接收器,它配套有一个SendPort发送器, 当前Isolate可以把SendPort发送器送给其他Isolate,其他Isolate通过这个SendPort发送器就可以发送消息给当前Isolate了。

  2. 当前Isolate给其他Isolate发消息的实现逻辑: 其他Isolate通过当前IsolateSendPort发送器发送一个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的官方介绍如下图:

  • 静态示意图

执行完main()函数后将会创建一个Main Isolate

  • 动态示意图

  • Event Loop会处理两个队列MicroTask queueEvent queue中的任务;

  • Event queue主要处理外部的事件任务:I/O,手势事件,定时器,isolate间的通信等;

  • MicroTask queue主要处理内部的任务:譬如处理I/O事件的中间过程中可能涉及的一些特殊处理等;

  • 两个队列都是先进先出的处理逻辑,优先处理MicroTask queue的任务,当MicroTask queue队列为空后再执行Event queue中的任务;

  • 当两个队列都为空的时候就进行GC操作,或者仅仅是在等待下个任务的到来。

为了比较好的理解 Event Loop 的异步逻辑,我们来打个比喻:就像我去长沙某网红奶茶品牌店买杯“幽兰拿铁”(由于是现做的茶,比较耗时)的过程。

  1. 我来到前台给服务员说我要买一杯你们店的“幽兰拿铁”,然后服务员递给了我一个有编号的飞盘(获取凭证);
  1. 奶茶店的备餐员工就将我的订单放在订单列表的最后面,他们按照顺序准备订单上的商品,准备好一个就让顾客去领取(Event queue 先进先出进行处理),而我就走开了,该干啥干啥去了(异步过程,不等待处理结果);
  1. 突然他们来了个超级VIP会员的订单,备餐员工就把这个超级VIP订单放在了其他订单的最前面,优先安排了这个订单的商品(MicroTask优先处理)—此场景为虚构;
  1. 当我的订单完成后,飞盘开始震动(进行结果回调),我又再次回到了前台,如果前台妹子递给我一杯奶茶(获得结果),如果前台妹子说对不起先生,到您的订单的时候没水了,订单没法完成了给我退钱(获得异常错误错误)。

我们常用的异步操作Future,async,await都是基于Event Loop,我们接下来就来介绍他们异步操作背后的原理。

Future

我们接下来用代码总体说明一下Future背后的逻辑:

final myFuture = http.get(‘https://my.image.url’);

myFuture.then((resp) {

setImage(resp);

}).catchError((err) {

print(‘Caught $err’); // Handle the error.

});

// 继续其他任务

  1. http.get('https://my.image.url')返回的是一个未完成状态的Future, 可以理解为一个句柄,同时http.get('https://my.image.url')被丢进了Event queue中等待被执行,然后接着执行当前的其他任务;
  1. Event queue执行完这个get请求成功后会回调then方法,将结果返回,Future为完成状态 ,就可以进行接下来的操作了;
  1. Event queue执行完这个get请求失败后会回调catchError方法,将错误返回,Future为失败状态 ,就可以进行错误处理了。

我们接下来分别介绍下Future的一些相关函数:

构造函数

  • Future(FutureOr<T> computation())

final future1 = Future(() {

return 1;

});

computation被放入了Event queue队列中

  • Future.value

final future2 = Future.value(2);

值在MicroTask queue队列中返回

  • Future.error(Object error, [StackTrace? stackTrace])

final future3 = Future.error(3);

这个error表示出现了错误,其中的值不一定需要给一个Error对象

  • Future.delay

final future4 = Future.delayed(Duration(seconds: 1), () {

return 4;

});

延迟一定时间再执行

Future结果回调then

final future = Future.delayed(Duration(seconds: 1), () {

print(‘进行计算’);

return 4;

});

future.then((value) => print(value));

print(‘继续进行接下来的任务’);

// flutter: 继续进行接下来的任务

// flutter: 进行计算

// flutter: 4

Future出现错误后的回调onError

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

面试复习路线,梳理知识,提升储备

自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。

关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

  • 架构师筑基必备技能
  • Android高级UI与FrameWork源码
  • 360°全方面性能调优
  • 解读开源框架设计思想
  • NDK模块开发
  • 微信小程序
  • Hybrid 开发与Flutter

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结:

Android开发七大模块核心知识笔记

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

606658)]

《960全网最全Android开发笔记》

[外链图片转存中…(img-N2ZYddvV-1713357606659)]

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

[外链图片转存中…(img-rDo5ndhY-1713357606660)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值