前言

最近用 Flutter 写了一段时间的业务代码,遇到了很多之前写简单代码没遇到的问题,比如说:

  • 如何使用 Flutter 调原生
  • 如何选择状态管理和事件管理
  • 如何画出自己想要的View
  • ...

表情包

上面中的很多场景,都会涉及到异步知识。

我们在写 Flutter 的时候,也会需要异步处理问题,比如说文件处理、网络请求、加载图片等。

一、Flutter中的异步机制

isolate 这个词对于 Flutter 新手来说可能有些陌生,它其实是 Dart 中的线程机制。

1. 单线程模型

Dart 是一种基于单线程模型的语言,它的线程模型是这样的:

An image showing Dart’s event loop. Flutter isolate

正如上面图中所表示的那样。先看左边,每个 isolate 维护着一个事件循环,事件循环由两个队列组成:

  1. microtask queue:只处理当前 isolate 中的任务,优先级高
  2. event queue:相应点击事件、IO事件、网络事件等的任务队列,优先级低

从右边的图中,我们可以看出,isolate 会先执行 microtask queue 中的任务,之后才会处理 event queue 中的任务,没有任务以后,isolate 才会结束。

2. main isolate

每个应用都由 1 个 main isolate 和 0 - 多个 work isolate 组成,main isolate 跟 Android 中的主线程一样,会处于无限循环的状态:

A figure showing a main isolate, which runs <code loading=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<String> 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<R> then<R>(FutureOr<R> onValue(T value), {Function? onError})

意味处理多个连续请求,我们可以使用 then 避免陷入回调地狱:

// 模拟网络请求
Future<String> requestNetwork(String name){
  return Future.delayed(Duration(seconds: 1), (){
    return "Hello, i am $name";
  });
}

// 模拟数据库操作
Future<String> 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<T> computation())将 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<String>(
          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 有没有完成,主要关心三个状态:

  1. ConnectionState.done 成功:Future 正常完成
  2. ConnectionState.done 错误:错误态
  3. 非完成态

三、async/await

如果说 Future 有响应式编程的那味儿,那么 async/await 就是不折不扣的协程味儿。

通过 async/await 我们可以使用同步的方式写出异步的代码,比如上述网络请求后存数据库:

Future<String> 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<String> createStream() async* {
  for(int i = 0; i < 10; i++) {
    String result = await requestNetwork("num: $i");
    yield result;
  }
}

注意,方法右边使用的 async* 关键字,而不是 async,请求的结果使用 yield 发送。

1.2 StreamController

StreamController 的功能更加强一点:

  1. Stream使用更加灵活
  2. 可以缓存发射的数据

结构如图:

Stream图片

主要分为四个角色:

  1. StreamController:控制整个 Stream 流程
  2. Stream:数据源,可被监听,Single-Subscription 只能被监听一次,Broadcast Stream 可以被多次监听
  3. StreamSink:用来添加数据的地方
  4. 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<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream as Stream<T>;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }

  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<StatePage> createState() => _StatePageState();
}

class _StatePageState extends State<StatePage> {

  String str = "JiuXin";
  late StreamSubscription<RequestEvent> _subscription;

  @override
  void initState() {
    super.initState();

    _subscription = bus.on<RequestEvent>().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")) 即可,需要注意的是,在 StatefulWidgetdispose 周期中,我们需要取消对应的监听。

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<RequestEvent>(
        stream: bus.on<RequestEvent>(),
        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 发布!

九心
1 声望1 粉丝