Flutter进阶之全面深入理解Stream

通过上一篇文章我相信你也能了解到了Stream的用处,但是可能还是太懵,因为我说的有点多,就是一个简单的案例。刚好昨天晚上我翻了翻我曾经看过的大佬的文章,就找到了上篇文章所说的那个大佬。

感觉我看过的大佬都是喜欢用喵星人做为头像。

好了我们步入正题:

全面深入理解Stream

一. Stream由深入浅

Stream的基础概念和重要性我就不说了上一篇文章说过!!!

我们看下整个Stream设计外部暴露的对象图:

1. Stream的简单使用

如下代码所示,Stream的使用并不复杂,一般我们只需要:

  • 创建StreamController
  • 然后获取StreamSink用做时间入口
  • 获取Stream对象用于监听
  • 并且通过监听得到StreamSubscription管理事件订阅,最后在不需要时关闭计科,看起来是不是很简单?
class DataBloc {
  ///定义一个Controller
  StreamController<List<String>> _dataController = StreamController<List<String>>();
  ///获取 StreamSink 做 add 入口
  StreamSink<List<String>> get _dataSink => _dataController.sink;
  ///获取 Stream 用于监听
  Stream<List<String>> get _dataStream => _dataController.stream;
  ///事件订阅对象
  StreamSubscription _dataSubscription;

  init() {
    ///监听事件
    _dataSubscription = _dataStream.listen((value){
      ///do change
    });
    ///改变事件
    _dataSink.add(["first", "second", "three", "more"]);

  }

  close() {
    ///关闭
    _dataSubscription.cancel();
    _dataController.close();
  }
}

在设置好监听后,之后每次有事件变化时,listen内的方法就会被调用,同时你还可以通过操作符对Stream进行变换处理。

如下代码所示,是不是有一股rx风扑面而来?

_dataStream.where(test).map(convert).transform(streamTransformer).listen(onData);

而在Flutter中,最后结合StreamBuilder,就可以完成基于事件流的异步状态控件了!

StreamBuilder<List<String>>(
    stream: dataStream,
    initialData: ["none"],
    ///这里的 snapshot 是数据快照的意思
    builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
      ///获取到数据,为所欲为的更新 UI
      var data = snapshot.data;
      return Container();
    });

俺么问题来了,它们内部究竟是如何实现的呢?原理是什么?各自的作用是什么?都有哪些特性呢?后面我们将开始深入解析这个逻辑。

2. Stream四大天王

从上面我们知道,在Flutter中使用Stream主要有四个对象,那么这四个对象是如何“勾搭”在一起的?他们各自又担任什么职责呢?

首先如下图,我们可以从姐姐般的流程图上看出整个Stream的内部工作流程。

Flutter中StreamStreamControllerStreamSinkStreamSubscription 都是 abstract 对象,他们对外抽象出接口,而内部实现对象大部分非都是**_开头的如_SyncStreamController** 、ControllerStream等私有类,在这基础上整个流程概括起来就是:

有一个事件源叫Stream,为了方便控制Stream,官方提供了使用StreamController作为管理;同时它对外提供了StreamSink对象作为时间输入口,可通过sink属性访问;又提供stream属性提供Stream对象的监听和变换,最后得到的StreamSubscription可以管理事件的订阅。

所以我们可以总结出:

  • StreamController : 如类名描述,用于整个Stream过程的控制,提供各类接口用于创建各种事件流。
  • StreamSink: 一般作为事件的入口,提供如add,addStream等。
  • Stream: 事件源本身,一般可用于监听事件或者对事件进行转移,如listenwhere
  • StreamSubscription: 事件订阅后的对象,表面上可用于管理订阅过等各类操作,如cancelpause,同时在内部也是事件的中转关键。

回到Stream的工作流程上,在上图中我们知道,通过StreamSink.add添加一个事件时,事件最后回调到listen中的onData方法,这个过程是通过zone.runUnaryGuarded执行的,这里zone.runUnaryGuarded是什么作用后面再说,我们需要知道这个onData是怎么来的?

如上图,通过源码我们知道:

    1. Streamlisten的时候传入了onData回调,这个回调会传入到StreamSubscription中,之后通过zone.registerUnaryCallback注册得到**_onData**对象(不是前面的onData回调哦)。
    1. StreamSInk在添加事件时,会执行到StreamSubscription中的**_sendData方法,然后通过_zone.runUnaryGuarded(_onData, data);执行1中得到的_onData对象,触发listen**时传入的回调方法。

可以看出整个流程都是和StreamSubscription相关的,现在我们已经知道从事件入口到事件出口的整个流程是怎么运作的,那么这个过程是怎么异步执行的呢?其中频繁出现的zone是什么?

3. 线程

首先我们需要知道,Stream是怎么弄实现异步的?

这就需要说到Dart中的异步实现逻辑了,因为Dart是单线程应用,和大多数单线程应用一样,Dart是以消息循环机制来运行的,而这里面主要包含两个任务队列,一个是microtask内部队列,一个是event外部队列,而microtask的优先级又高于event

默认的在Dart中,如点击、滑动、IO、绘制事件等事件都属于event外部队列,microtask内部队列主要是由Dart内部产生,而Stream中执行异步的模式就是scheduleMicrotask了。

因为 microtask 的优先级又高于 event ,所以如果 microtask 太多就
可能会对触摸、绘制等外部事件造成阻塞卡顿哦。

如下图,就是Stream内部 在执行异步操作过程执行流程:

4. Zone

那么Zone又是什么?它是哪里来的?

由于Dart中Future之类的异步操作时无法被当前代码try/catch的,而在Dart中你可以给执行对象制定一个Zone,类似提供一个沙箱环境,而在这个沙箱内,你就可以全部捕获、拦截或修改一些代码行为,比如所有未被处理的异常。

那么项目中默认的Zone是怎么来的?在Flutter中,Dart中的Zone启动是在_runMainZoned方法,如下代码所示_runMainZoned@pragma("vm:entry-point")注解表示该方式是给Engine调用的,到这里我们知道Zone是怎么来的了。

///Dart 中

@pragma('vm:entry-point')
// ignore: unused_element
void _runMainZoned(Function startMainIsolateFunction, Function userMainFunction) {
  startMainIsolateFunction((){
    runZoned<Future<void>>(····);
  }, null);
}

///C++ 中
if (tonic::LogIfError(tonic::DartInvokeField(
          Dart_LookupLibrary(tonic::ToDart("dart:ui")), "_runMainZoned",
          {start_main_isolate_function, user_entrypoint_function}))) {
    FML_LOG(ERROR) << "Could not invoke the main entrypoint.";
    return false;
}

那么zone.runUnaryGuarded的作用是什么?相较于scheduleMicrotask的异步操作,官方的解释是:在此区域中使用参数执行给定操作并捕获同步错误。类似的还有runUnaryrunBinaryGuarded等,所以我们知道前面提到的zone.runUnaryGuarded就是Flutter在运行的这个zone里执行已经注册的_onData,并捕获异常。

5. 异步和同步

前面我们说了Stream的内部执行流程,那么同步和异步操作时又有什么区别?具体实现是怎么样的呢?

我们以默认Stream流程为例子,StreamController的工厂创建可以通过sync指定同步还是异步,默认是异步模式的。而无论同步还是异步,他们都是继承了**_StreamController对象,区别还是在于mixins的是哪个_EventDispatch**实现:

  • _AsyncStreamControllerDispatch
  • _SyncStreamControllerDispatch

上面这两个**_EventDispatch最大的不同就是在调用sendData提交事件时,是直接调用StreamSubscription**的_add方法,还是调用_addPending(new _DelayedData<T>(data));方法的区别。

如下图,异步执行的逻辑就是上面说过的scheduleMicrotask,在_StreamImplEventsscheduleMicrotask执行后,会调用_DelayedDataperform,最后通过_sendData触发StreamSubscription去回调数据。

6. 广播和非广播

Stream中有广播模式和非广播模式,如果是广播模式中,StreamControlle的实现是由如下所示实现的,他们的基础关系如下图所示:

  • _SyncBroadcastStreamController
  • _AsyncBroadcastStreamController

广播和非广播的区别在于调用_createSubscription时,内部对接口类_StreamControllerLifecycle的实现,同时他们的差异在于:

  • 在**_StreamController里判断了如果Stream_isInitialState的,也就是被订阅过的,就直接报错"Stream has already been listened to." ,只有未订阅的才创建StreamSubscription**。
  • 在**_BroadcastStreamController中,_isInitialState的判断去掉了,取而代之的是isClosed判断,并且在广播中,_sendData是一个forEach**执行:
_forEachListener((_BufferingStreamSubscription<T> subscription) {
    subscription._add(data);
  });

7. Stream变换

Stream是支持变换处理的,针对Stream我们可以经过多次变化来得到我们需要的结果。那么这些变化时怎么实现的呢?

如下图所示,一般操作符变化的Stream实现类,都是继承了**_ForwardingStream**,在它的内部的**_ForwardingStreamSubscription里,会通过上一个Pre A Streamlisten添加_handleData回调,之后在回调里再次调用新的Current B Stream_handleData**。

所以事件变化的本质就是,变换都是对Streamlisten嵌套调用组成的。

同时Stream还有转换为Future,如firstWhere 、elementAt、reduce等操作符方法,基本都是创建一个内部**_Future实例,然后再listen的回调调用Future**方法返回。

二. StreamBuilder

如下代码所示,在Flutter中通过StreamBuilder构建Widget,只需要提供一个Stream实例即可,其中AsyncSnapshot对象作为数据快照,通过data缓存了当前数据和状态,那StreamBuilder是如何与Stream关联起来的呢?

StreamBuilder<List<String>>(
    stream: dataStream,
    initialData: ["none"],
    ///这里的 snapshot 是数据快照的意思
    builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
      ///获取到数据,为所欲为的更新 UI
      var data = snapshot.data;
      return Container();
    });

如上图所示,StreamBuilder的调用逻辑主要在**_StreamBuilderBaseState中,_StreamBuilderBaseStateinitState、didUpdateWidget中会调用_subscribe方法,从而调用Streamlisten**,然后通过setState更新UI买就是这么简单有木有?

我们常用的 setState 中其实是调用了 markNeedsBuild ,
markNeedsBuild 内部标记 element 为 diry ,然后在下一帧 
WidgetsBinding.drawFrame 才会被绘制,这可以看出 setState 并不是立即生效的哦。

三. rxdart

其实无论从订阅或者变换都可以看出,Dart中的Stream已经自带了类似rx的效果,但是为了让rx的用户们更方便的使用,ReactiveX就封装了rxdart来满足用户的熟悉感,如下图所示为他们的对应关系:

rxdart中,Observable是一个Stream,而Subject继承了Observable也是一个Stream,并且Subject实现了StreamController的接口,所以它也具有Controller的作用。

如下代码所示是rxdart的简单使用,可以看出他屏蔽了外界需要对StreamSubscriptionStreamSink等的认知,更符合rx历史用户的理解。

final subject = PublishSubject<String>();

subject.stream.listen(observerA);
subject.add("AAAA1");
subject.add("AAAA2"));

subject.stream.listen(observeB);
subject.add("BBBB1");
subject.close();

这里我们简单的分析下,以上方代码为例,

  • PublishSubject内部实际创建是创建了一个广播StreamController< T >.broadcast
  • 当我们调用add或者addStream时,最终会调用到的还是我们创建的StreamController.add
  • 当我们调用onListen时,也是将回调设置到StreamController中。
  • rxdart在做变换时,我们获取到的Observable就是this,也就是PublishSubject自身这个Stream,而Observable一系列的变换,越是基于创建时传入的stream对象,比如:
  @override
  Observable<S> asyncMap<S>(FutureOr<S> convert(T value)) =>
      Observable<S>(_stream.asyncMap(convert));

所以我们可以看出来,rxdart只是对Stream进行了概念变换,变成了我们熟悉的对象和操作符,而这也是为什么rxdart可以在StreamBuilder中直接使用的原因。

所以,到这里你对Flutter种Stream有全面的理解了没?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值