前言
最近用 Flutter 写了一段时间的业务代码,遇到了很多之前写简单代码没遇到的问题,比如说:
- 如何使用 Flutter 调原生
- 如何选择状态管理和事件管理
- 如何画出自己想要的View
- ...
上面中的很多场景,都会涉及到异步知识。
我们在写 Flutter 的时候,也会需要异步处理问题,比如说文件处理、网络请求、加载图片等。
一、Flutter中的异步机制
isolate
这个词对于 Flutter 新手来说可能有些陌生,它其实是 Dart 中的线程机制。
1. 单线程模型
Dart 是一种基于单线程模型的语言,它的线程模型是这样的:
正如上面图中所表示的那样。先看左边,每个 isolate 维护着一个事件循环,事件循环由两个队列组成:
- microtask queue:只处理当前 isolate 中的任务,优先级高
- event queue:相应点击事件、IO事件、网络事件等的任务队列,优先级低
从右边的图中,我们可以看出,isolate 会先执行 microtask queue 中的任务,之后才会处理 event queue 中的任务,没有任务以后,isolate 才会结束。
2. main isolate
每个应用都由 1 个 main isolate 和 0 - 多个 work isolate 组成,main isolate 跟 Android 中的主线程一样,会处于无限循环的状态:
main(), responds to events, and then exits" title="A figure showing a main isolate, which runs main()
, responds to events, and then exits">
3. 与 Android 线程机制对比
跟 Android 原生的线程机制对比,多线程共享内存的那一套在 Dart 上行不通了。
从内存这块儿来看,每一个 isolate
更像是一个进程,内存独立隔离,无法互相访问状态,也正是因为这个,我们也不用去考虑互斥锁和其他锁的问题。
记住上面的优先级: microtask queue > event queue,我们开始学习代码!
二、Future
Futrue
是 Dart 异步编程的核心之一,表示一个不会立即返回的结果。
1. 使用介绍
一般这么使用:
// 模拟网络请求
Future requestNetwork(String name){
return Future.delayed(Duration(seconds: 1), (){
return "Hello, i am $name";
});
}
void doSomeThing() {
requestNetwork("JiuXin")
.then((value) => print(value))
.catchError((error, stackTrace) => print(stackTrace));
}
我们用 Future.delayed
延时模拟网络请求。
上面的代码使用了链式调用,在 then
方法中,我们可以获取异步返回的结果,在 onError
方法中,我们可以处理捕获的异常。
2. 处理多个请求
then
方法是这样的:
Future then(FutureOr onValue(T value), {Function? onError})
意味处理多个连续请求,我们可以使用 then
避免陷入回调地狱:
// 模拟网络请求
Future requestNetwork(String name){
return Future.delayed(Duration(seconds: 1), (){
return "Hello, i am $name";
});
}
// 模拟数据库操作
Future saveInDB(String name) {
return Future.delayed(Duration(seconds: 1), (){
// 处理数据库操作
return "save in DB success, Hello, i am $name";
});
}
void doSomeThing() {
requestNetwork("JiuXin")
.then((value) => saveInDB(value))
.then((value) => print(value))
.catchError((error, stackTrace) => print(stackTrace));
}
3. 一些通用 Api
除了上述的 Future.delayed方法,还有一些 factory
方法:
方法 | 介绍 |
---|---|
Future(FutureOr |
将 Future 放入 event queue |
Future.microtask | 将 Future 放入 microtask queue |
Future.sync | 立刻执行 Future 里面的完成代码 |
上述的方法主要影响的是 Future 完成的时机。
4. FutureBuilder
通过 FutureBuilder,我们可以在 StatelessWidget 展现出不同的状态,还是上面的代码:
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TestPage"),
),
body: Center(
child: FutureBuilder(
future: requestNetwork("JiuXin").then((value) => saveInDB(value)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text("onError: ${snapshot.error}");
} else {
return Text(snapshot.data ?? "");
}
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
对于 Future
来说,我们只要关注 Future 有没有完成,主要关心三个状态:
- ConnectionState.done 成功:Future 正常完成
- ConnectionState.done 错误:错误态
- 非完成态
三、async/await
如果说 Future
有响应式编程的那味儿,那么 async/await
就是不折不扣的协程味儿。
通过 async/await
我们可以使用同步的方式写出异步的代码,比如上述网络请求后存数据库:
Future solveNetwork() async {
String netStr = await requestNetwork("JiuXin");
String nextStr = await saveInDB(netStr);
return nextStr;
}
四、Stream
Future
是异步的一个核心,用来表示一个异步事件。
Stream
则是异步的另外一个核心,用来表示一系列异步事件。
1. 创建 Stream
创建 Stream 一般有两种方式:使用 yield 和 StreamController。
1.1 yield
使用 yield 方式比较简单,比如我发送十次请求结果:
Stream createStream() async* {
for(int i = 0; i < 10; i++) {
String result = await requestNetwork("num: $i");
yield result;
}
}
注意,方法右边使用的 async*
关键字,而不是 async
,请求的结果使用 yield
发送。
1.2 StreamController
StreamController
的功能更加强一点:
- Stream使用更加灵活
- 可以缓存发射的数据
结构如图:
主要分为四个角色:
- StreamController:控制整个 Stream 流程
- Stream:数据源,可被监听,Single-Subscription 只能被监听一次,Broadcast Stream 可以被多次监听
- StreamSink:用来添加数据的地方
StreamSubscription:监听 Stream 生成的对象,可取消
StreamController 使用流程如下,参考 EventBus:
class EventBus {
StreamController _streamController;
StreamController get streamController => _streamController;
StreamSink get _streamSink => _streamController.sink;
// 如果想使用Single-Subscription,_streamController = StreamController()
// 如果想使用BroadCast,StreamController.broadcast(sync: sync)
EventBus({bool sync = false})
: _streamController = StreamController.broadcast(sync: sync);
EventBus.customController(StreamController controller)
: _streamController = controller;
Stream on() {
if (T == dynamic) {
return streamController.stream as Stream;
} else {
return streamController.stream.where((event) => event is T).cast();
}
}
void fire(event) {
_streamSink.add(event);
}
void destroy() {
_streamController.close();
}
}
使用处:
EventBus bus = EventBus(sync: true);
class RequestEvent{
String content;
RequestEvent({required this.content});
}
class StatePage extends StatefulWidget {
const StatePage({Key? key}) : super(key: key);
@override
State createState() => _StatePageState();
}
class _StatePageState extends State {
String str = "JiuXin";
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = bus.on().listen((event) {
setState(() {
str = event.content;
});
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Text(str),
);
}
@override
void dispose() {
super.dispose();
_subscription.cancel();
}
}
在我们想要的调用点使用 bus.fire(RequestEvent("content"))
即可,需要注意的是,在 StatefulWidget
的 dispose
周期中,我们需要取消对应的监听。
Stream 跟 Rx 系列很相似,并且也有很多其他方便的 api 供我们调用,感兴趣的可以自己看一下。
2. StreamBuilder使用
StreamBuilder 和 FutureBuilder 有点像,又有点不像。
从整个使用流程来说,它们是一样的,对于状态的监听来说,它们是不一致的。
对于上面的 EventBus,同一个页面,如果我们想在 StreamBuilder 中使用:
class PageOne extends StatelessWidget {
PageOne({Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder(
stream: bus.on(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("onError: ${snapshot.error}");
}
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text("暂时没有数据哦~");
case ConnectionState.waiting:
return CircularProgressIndicator();
case ConnectionState.active:
return Text('${snapshot.data?.content ?? ""}');
case ConnectionState.done:
return Text('Stream 已关闭');
}
},
),
);
}
}
解释一下:
- ConnectionState.active:Event 发送成功
ConnectionState.done:当 StreamSink 关闭后
FutureBuilder 是在 ConnectionState.done 以后接受数据。
总结
Dart 中的异步方式还是挺简单的,如果有疑惑,我们评论区见。
本文由博客一文多发平台 OpenWrite 发布!