Flutter| 事件循环,Fluture

牢骚

故乡容不下灵魂,他乡容不下肉身!若能一世安稳,谁愿颠沛流离。

正文

在 Dart 中,没有多线程的概念,所谓的异步操作全部都是在一个线程里面执行的, 并且不会造成卡顿的原因就是事件循环(Event Loop),

如下图所示,在程序的运行过程中,会有两个事件

image-20210526161058520

补充上图:Micortask Queue 为空 才会执行 EventQueue ,EventQueue 为空时程序结束,实际上,事件循环从启动的之后会一直执行。

在程序执行过程中,如果有异步操作,这个操作就会添加到队列中,当发现队列不为空时,就会然后不断的从队列中取出事件在执行

Microtask Queue

一个顶级的队列,只要这个队列里面不是空的,就一定会执行该队列中的任务,

scheduleMicrotask(() {
  print("Hello Flutter");
});
Future.microtask() //内部调用的也是上面的函数

但是需要注意的是,一般的实战中,我们不会手动给这个队列里面添加事件,该队列一般都是由 Dart 自己来处理的。

Event Queue

普通的事件队列,比 Microtask Queue 低了一个等级,在 Microtask Queue 中没有任务的时候才会执行该队列中的任务

需要异步操作的代码都会放在 EventQueue 中执行,例如:

Future((){
	print('这里会被放在 EventQueu 中执行');
})

Future.delayed(Duration(seconds: 1), () {
	print('这里会被放在 EventQueu 中执行');
});

直接执行的代码

Future.sync(() => print('Hello'));
Future.value(() => print('world'));
xxx.then()

Future

Flutter 相当于是一个盒子,内部的代码最终会交给 EventQueue 来执行,Future 的状态大致可分为三种,如下:

Future(() {
    print('未完成状态');
})
.then((value) => print('已完成状态'))
.catchError((value) => print('异常状态'));

我们程序中的大部分异步操作都是围绕着这三种状态进行的。

Future 常用的函数

  • Future.error()

    Future(() {
      return Future.error(Exception());
    }).then((value) => print('已完成状态')).catchError((value) => print('异常状态'));
    

    创建一个以异常结束的 Future,上面代码最终会执行到 catchError 中。

  • Future.whenComplete()

    类似于 try catch 后面的 finnaly,无论成功和失败,最终都会执行到这里

  • Future.them 链式调用

    //在 them 中可以接继续返回值,该值会在下一个链式的 then 调用中拿到返回的结果
    getNetData().then((value) {
      //支持成功到此处
      print(value);
      return "data1";
    }).then((value) {
      print(value);
      return "data2";
    }).then((value) {
      print(value);
    }).catchError((error) {
      //执行失败到此处
      print(error);
    }).whenComplete(() => print("完成"));
    
  • Future.wait()

    如果要等到多个异步任务都结束之后再进行一些操作,可以使用 Future.wait

    Future.wait([getNetData(), getNetData(), getNetData()]).then((value) {
      value.forEach((element) {
        print(element);
      });
    });
    
  • Future.delayed()

    延时指定的时间后在执行

     Future.delayed(Duration(seconds: 3), () {
        return "3秒后的信息";
      }).then((value) {
        print(value);
      });
    
  • async,await

    async:用来表示函数是异步的,定义的函数会返回一个 Future 对象,可以使用 then 添加回调函数

    await :后面是一个 Future,表示等待改异步任务的完成,异步完成之后才会继续往下走,await 必须出现在 async 的内部

    
    void main() {
      print("start ----------->");
    
      getNetData().then((value) => print(value));
    
      print("end -------------->");
    }
    
    // async 会将返回的结果封装为 Future 类型
    getNetData() async {
      var result1 = await Future.delayed(Duration(seconds: 3), () {
        return "网络数据1";
      });
      var result2 = await Future.delayed(Duration(seconds: 3), () {
        return "网络数据2";
      });
      return result1 + "-----" + result2;
    }
    

FutureBuilder

监听一个 Future,以 Future 的状态来进行 setState

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: Future.value(10),
        builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
          return DemoWidget()
        });
  }
}

构造

  • future :接受一个 future,当 future 的值发生变化之后,就会自动调用下面的 build 函数,

  • initialData:初始值,在 future 没完成的时候可以暂时使用该值,该值会放在 AsyncSnapshot 的 data 中,在 future 未完成的时候可以使用该值。在 future 出错的时候,该值会被 AsyncSnapshot 从 data 中删掉

  • builder:返回一个 Widget

    AsyncSnapshot 用来保存 future 最近的状态,这个状态只有两个情况,一种是有数据 data,一种是错误状态 error。 AsyncSnapshot 中还有 ConnectionState 状态,分别表示的是 none :没有传递 future,waiting:等待中,active:TODO ,done :表示已经完成

FutureBuilder 的作用就是根据 future 的状态来判断当前页面需要显示哪些 widiget,例如 future 在等待的时候显示加载框,完成之后显示内容等。

需要注意的一点是当状态为 done 是,可能会有两种情况,一种是 future 成功了,另一种是 future 失败了,内部有异常,这个时候就不应该获取 data,而是判断 snap.hasData 来进行判断。

示例

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: Future.value(10),
        builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
          if (snap.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          }
          if (snap.connectionState == ConnectionState.done) {
            if (snap.hasData) {
              return DemoWidget();
            } else {
              return Text(snap.error);
            }
          }
          throw "should not happen";
        });
  }
}
//在精简一些
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: Future.value(10),
        builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
          //如果有数据  
          if (snap.hasData) {
            return DemoWidget();
          }
           //如果发生错误
          if (snap.hasError) {
            return Text(snap.error);
          }
          // 等待中,显示加载框
          return CircularProgressIndicator();
        });
  }
}

源码浅析

/// State for [FutureBuilder].
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
	//....
  @override
  void initState() {
      //如果没有初始值,则先设置Wie none 状态,如果有,则传入初始值 
        _snapshot = widget.initialData == null
        ? AsyncSnapshot<T>.nothing()
        : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
    _subscribe();
  }

  @override
  void didUpdateWidget(FutureBuilder<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.future != widget.future) {
     //....
      _subscribe();
    }
  }
  void _subscribe() {
    if (widget.future != null) {
      final Object callbackIdentity = Object();
      _activeCallbackIdentity = callbackIdentity;
      widget.future!.then<void>((T data) {
        if (_activeCallbackIdentity == callbackIdentity) {
          //任务执行完成,将数据传给 _snapshot ,并刷新
          setState(() {
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
          });
        }
      }, onError: (Object error, StackTrace stackTrace) {
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
              //出现错误
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
          });
        }
        assert(() {
           //判断调试状态 
          if(FutureBuilder.debugRethrowError) {
            Future<Object>.error(error, stackTrace);
          }
          return true;
        }());

      });
       //没有完成则是等待状态
      _snapshot = _snapshot.inState(ConnectionState.waiting);
    }
  }

源码其实很简单,仔细看一下就知道整个流程了

StreamBuilder

介绍

上面的 FutureBuilder 只能给我们一个值,而 StreamBuildder 可以给我们一连串的值 ,例如:

final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);
stream.listen((event) {
  print(event);
});

示例

class _MyHomePageState extends State<MyHomePage> {
  final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      color: Colors.white,
      child: DefaultTextStyle(
        style: TextStyle(fontSize: 30, color: Colors.black),
        child: StreamBuilder(
            stream: stream,
            builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
              switch (snap.connectionState) {
                case ConnectionState.none:
                  return Text("NONE:没有数据流");
                  break;
                case ConnectionState.waiting:
                  return Text("WAITING:等待数据流");
                  break;
                case ConnectionState.active:
                  if (snap.hasData) {
                    return Text(snap.data.toString());
                  }
                  if (snap.hasError) {
                    return Text(snap.error.toString());
                  }
                  break;
                case ConnectionState.done:
                  return Text("DONE:数据流已关闭");
                  break;
              }

              return CircularProgressIndicator();
            }),
      ),
    );
  }
}

其实和 FutureBuilder 差不多,只不过多了一个 active 状态,这个状态在上面没有说是因为用不到**,在这里的意思指的就是数据流是否为活跃的**,如果是活跃的,则就可以获取他的值了

创建方式及常用的函数

  • 使用 Stream.periodic 的方式来创建一个数据流,如上面的示例所示

  • 读取文件的方式

    File("").openRead().listen((event) { 
      
    })
    

    将读取的文件信息以数据流的方式转给我们

  • 使用 StreamController

    final controller = StreamController();
    controller.stream.listen((event) {
      print('$event');
    });
    
    controller.sink.add(12);
    controller.sink.add(13);
    controller.sink.add(14);
    controller.sink.add(15);
    controller.sink.addError("Error");
    
    //关闭后则不能进行任何添加操作
    controller.close();
    

    StreamController 这种方式就比 periodic 创建的方式好多了,可以自由的往数据流中添加数据。

    需要注意的是使用完成之后要进行关闭操作,否则就会泄漏资源 并且 flutter 会一直警告,

    上面的这种方式只能有一个监听,如果添加多个监听则就会保存,那么如何添加多个监听呢,可以使用广播的方式,如下:

    final controller = StreamController.broadcast();
    

    只需要在创建的时候使用 broadcast() 即可

    这两者有一个非常明显的区别就是缓存,默认的创建方式是有缓存的,而 broadcast 则没有缓存,如下:

    final controller = StreamController();
    
    controller.sink.add(12);
    controller.sink.add(13);
    controller.sink.add(14);
    controller.sink.add(15);
    controller.sink.addError("Error");
    
    controller.stream.listen((event) {
      print('$event');
    }, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE'));
    
    //关闭后则不能进行任何添加操作
    controller.close();
    
    • 上面的这种方式,即使是先添加了数据,在回调中也会打印出之前添加的数据

      final controller = StreamController.broadcast();
      
      controller.sink.add(12);
      controller.sink.add(13);
      controller.sink.add(14);
      controller.sink.add(15);
      controller.sink.addError("Error");
      
      controller.stream.listen((event) {
        print('$event');
      }, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE'));
      controller.stream.listen((event) {
        print('Copy:$event');
      });
      
      //关闭后则不能进行任何添加操作
      controller.close();
      

      而这种方式不会打印之前的数据。这两种方式就好像 EventBus 中的粘性事件 和 非粘性事件,每种都有它的作用另外,

    • map

      使用 map 还可以将事件进行改变或者修改,如下:

      controller.stream.map((event) => "Map: $event").listen((event) {
        print('$event');
      });
      
    • where

      除过 map 方法之外,还有一个比较有用的方法是 where ,可以对事件进行过滤

      controller.stream
          .where((event) => event > 13)
          .map((event) => "Map: $event")
          .listen((event) {
        print('$event');
      });
      
    • distinct

      去重,如何和上次的数据相同,则不会收到该事件‘

    • 语法糖 async*

      类似于 async await 的使用,如下

      void main() {
        getTime().listen((event) {
          print('$event');
        });
      }
      
      Stream<DateTime> getTime() async* {
        while (true) {
          Future.delayed(Duration(seconds: 1));
          yield DateTime.now();
        }
      }
      

源码浅析

StreamBuilder 继承自 StreamBuilderBase 的,核心如下

void _subscribe() {
  if (widget.stream != null) {
    _subscription = widget.stream!.listen((T data) {
      setState(() {
        _summary = widget.afterData(_summary, data);
      });
    }, onError: (Object error, StackTrace stackTrace) {
      setState(() {
        _summary = widget.afterError(_summary, error, stackTrace);
      });
    }, onDone: () {
      setState(() {
        _summary = widget.afterDone(_summary);
      });
    });
    _summary = widget.afterConnected(_summary);
  }
}

StreamBuilder 做的小游戏

在日常开发中,StreamBuilder 还是挺实用的,这次我们用 StreamBuilder 来做一个小游戏,先看效果:

345

从上面的动画来看,可以将其分为三个部分,第一个部分则是底部的键盘,第二部分就是向下落的题目,第三部分则是得分的结果。

下面我们来实现一下这个小游戏

  • KeyPad

    class KeyPad extends StatelessWidget {
      final StreamController inputController;
      final StreamController scoreController;
    
      const KeyPad(this.inputController, this.scoreController);
    
      @override
      Widget build(BuildContext context) {
        return GridView.count(
          crossAxisCount: 3,
          childAspectRatio: 2,
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          padding: EdgeInsets.all(0.0),
          children: List.generate(
            9,
                (index) =>
                FlatButton(
                  shape: RoundedRectangleBorder(),
                  onPressed: () {
                    inputController.sink.add(index + 1);
                    scoreController.add(-2);
                  },
                  child: Text("${index + 1}"),
                  color: Colors.primaries[index],
                ),
          ),
        );
      }
    }
    

    KeyPad 接收了两个 Stream,分别是输入和分数, 底部的键盘是一个 GridView,当点击到对应的按钮上时,则发送输入的数字,已经分数 -2,至于为啥减2,后面你就知道了。

  • Puzzle

    class Puzzle extends StatefulWidget {
      final Stream inputStream;
      final StreamController scoreController;
    
      const Puzzle({Key key, this.inputStream, this.scoreController})
          : super(key: key);
    
      @override
      _PuzzleState createState() => _PuzzleState();
    }
    
    class _PuzzleState extends State<Puzzle> with SingleTickerProviderStateMixin {
      int a, b;
      double x;
      Color color;
    
      AnimationController _controller;
    
      @override
      void initState() {
        _controller = AnimationController(vsync: this);
        reset();
        _controller.addStatusListener((status) {
          //动画结束
          if (status == AnimationStatus.completed) {
            reset(-100.0);
            widget.scoreController.sink.add(-3);
          }
        });
        widget.inputStream.listen((event) {
          if (event == a + b) {
            reset(-100.0);
            widget.scoreController.sink.add(7);
          }
        });
        super.initState();
      }
    
      reset([from = 0.0]) {
        a = Random().nextInt(5) + 1;
        b = Random().nextInt(5);
        x = Random().nextDouble() * 280;
        _controller.duration =
            Duration(milliseconds: Random().nextInt(5000) + 5000);
        color = Colors.primaries[Random().nextInt(Colors.primaries.length)][200];
        _controller.forward(from: from);
      }
    
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Positioned(
                top: 700 * _controller.value - 100,
                left: x,
                child: Container(
                  decoration: BoxDecoration(
                      color: color.withOpacity(0.5),
                      border: Border.all(color: Colors.black),
                      borderRadius: BorderRadius.circular(24)),
                  padding: EdgeInsets.all(8),
                  child: Text("$a + $b ", style: TextStyle(fontSize: 24)),
                ),
              );
            });
      }
    }
    

    Puzzle 也接收了输入和分数的 Stream,并且创建了一个动画,在 initState 中,监听动画和输入事件,动画结束则表示没有答对题,直接重置,并扣分,收到输入事件之后则 计算结果是否真确,然后重置,并且加分

    reset 方法中用于生产题目和 x 轴的位置以及动画的执行时间,最后开启动画

    build 中其实是很简单的,使用了 AnimatedBuilder 来监听动画,当动画值改变后则会重新 setState(),内部就是一个小按钮,记录了题目,注意背景颜色是 0.5 透明度

  • 分数计算和拼接上面两个 widget

    class GemsPage extends StatefulWidget {
      @override
      _GemsPageState createState() => _GemsPageState();
    }
    
    class _GemsPageState extends State<GemsPage> {
      final _inputController = StreamController.broadcast();
      final _scoreController = StreamController.broadcast();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: StreamBuilder(
              stream: _scoreController.stream.transform(TallyTransformer()),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text("得分: ${snapshot.data}");
                }
                return Text("得分: 0");
              },
            ),
          ),
          body: Stack(
            children: [
              ...List.generate(
                8,
                (index) => Puzzle(
                    inputStream: _inputController.stream,
                    scoreController: _scoreController),
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: KeyPad(_inputController, _scoreController),
              )
            ],
          ),
        );
      }
    
      @override
      void dispose() {
        _scoreController.close();
        _inputController.close();
        super.dispose();
      }
    }
    
    class TallyTransformer implements StreamTransformer {
      int sum = 0;
    
      StreamController _controller = StreamController();
    
      @override
      Stream bind(Stream<dynamic> stream) {
        stream.listen((event) {
          sum += event;
          _controller.add(sum);
        });
        return _controller.stream;
      }
    
      ///类型检查
      @override
      StreamTransformer<RS, RT> cast<RS, RT>() => StreamTransformer.castFrom(this);
    }
    

参考:B站王叔不秃

如果本文有帮助到你的地方,不胜荣幸,如有文章中有错误和疑问,欢迎大家提出!

猜你喜欢

转载自blog.csdn.net/baidu_40389775/article/details/121165427