Flutter异步与事件监听

异步

  • 首先Dart是单线程的
  • 开发中肯定会遇到很多耗时操作的,而单线程肯定不能一直在等待这些耗时操作完成,而造成阻塞
  • 那么Dart里是如何处理的呢,答:Dart是基于单线程加事件循环来完成耗时操作的处理的
事件循环
  • 将需要处理的事件,放到一个事件队列中,不断的从队列中取出对应的事件,并执行。

  • 跟咱iOS里的Runloop类似
    引用一下官方图,齿轮就是事件循环,不断的从左边取事件执行

  • 实际上Dart从两个队列执行任务:event事件队列和microtask微任务队列;

  • 而微任务队列的优先级要高于事件队列;

  • 所以是优先执行微任务队列中的任务,再执行 事件队列 中的任务

  • 一般来说,I/O事件,手势,Timer,绘制,其他外部事件都会放到event事件队列中,而微任务通常来源于Dart内部,并且很少,因为如果微任务太多,可能会阻塞事件队列,而导致一些用户操作可能就没有反应了。

  • 两个队列都是按照先进先出的顺序执行任务

Dart的异步操作
  • 主要使用Future以及asyncawait
首先先举个栗子,看一下同步的耗时操作
void main() {
  print('start ${DateTime.now()}');
  print('${sendRequest()} ${DateTime.now()}');
  print('end ${DateTime.now()}');
}

String sendRequest(){
  sleep(Duration(seconds: 2));
  return 'Request finish';
}

毫无疑问,sendRequest会阻塞main函数的执行,可以看到打印startRequest finish之间相隔了2秒

使用Future
void main() {
  print('start ${DateTime.now()}');
  print('${sendRequest()} ${DateTime.now()}');
  print('end ${DateTime.now()}');
}

Future<String> sendRequest(){
  return Future<String>((){
    sleep(Duration(seconds: 2));
    return 'Request finish';
  });
}

你会看到,sendRequest丝毫没有阻塞main函数的执行,直接连续print了3次,显然这才是我们需要的效果,但是打印的是一个Future实例,那么我们需要拿到最终的结果。

获取Future结果
  • 通过.then的回调
void main() {
  print('start ${DateTime.now()}');
  print('${sendRequest().then((value){
    print('$value ${DateTime.now()}');
  })} ${DateTime.now()}');

  print('end ${DateTime.now()}');
}

执行异常捕获
void main() {
  print('start ${DateTime.now()}');
  print('${sendRequest().then((value){
    print('$value ${DateTime.now()}');
  }).catchError((error){
    print('$error ${DateTime.now()}');
  })} ${DateTime.now()}');

  print('end ${DateTime.now()}');
}

Future<String> sendRequest(){
  return Future<String>((){
    sleep(Duration(seconds: 2));
    //return 'Request finish';
    throw Exception('出异常了');
  });
}

Future的链式调用
  • 可以在then中继续return,可以在下一个then中拿到结果
void main() {
  sendRequest().then((value) {
    print('11 - $value ${DateTime.now()}');
    return '海贼·王路飞';
  }).then((value) {
    print('22 - $value ${DateTime.now()}');
    return '三刀·流索隆';
  }).then((value) {
    print('33 - $value ${DateTime.now()}');
  });
}

Future<String> sendRequest(){
  return Future<String>((){
    sleep(Duration(seconds: 2));
    return 'Request finish';
  });
}

其他
  • Future.value:返回一个指定值的Future
void main() {
  print('start');
  Future.value('王路飞').then((value) {
    print('$value');
  });
  print('end');
}

  • Future.error:返回一个异常的 Future
void main() {
  print('start');
  Future.error(Exception('异常了')).then((value) {
    print('$value');
  });
  print('end');
}

image.png

  • Future.delayed:延迟执行
void main() {
  print('start ${DateTime.now()}');
  Future.delayed(Duration(seconds: 2)).then((value) {
    print('delayed ${DateTime.now()}');
  });
  print('end ${DateTime.now()}');
}

当然还有很多api,可以自行了解

await、async
  • Dart中的关键字,async声明函数内部有代码需延迟执行,await声明运算为延迟执行
  • async的函数需要返回一个Futureawait需在async函数中使用
  • 实际上是Dart异步编程用于简化异步API操作的,能够将异步的代码使用同步的代码结构实现
    举个栗子,如下,sleepTime的结果,我在sendRequest里需要拼接一个a给出去,可能如下写法
void main() {

  print('start ');
  print(sendRequest().then((value) {
    print(value);
  }));

  print('end ');
}

Future sendRequest() {

  final a = ' 12345';
  
  return sleepTime().then((value) {
    return value+a;
  });

}
///假设异步耗时请求
Future sleepTime(){
  return Future((){
    sleep(Duration(seconds: 2));
    return 'request finish';
  });
}

如果用await,async来写 看看

void main() {

  print('start ');
  print(sendRequest().then((value) {
    print(value);
  }));

  print('end ');
}

Future sendRequest() async{

  final a = ' 12345';
  var result = await sleepTime();
  
  return result + a;

}
///假设异步耗时请求
Future sleepTime(){
  return Future((){
    sleep(Duration(seconds: 2));
    return 'request finish';
  });
}

  • 这样可以像写同步代码一样去使用Future异步返回的结果
任务执行顺序
  • 顺序: main函数 -> 微任务队列(Microtask Queue)-> 事件队列(Event Queue)
  • 可以通过scheduleMicrotask创建一个微任务,Future是加到事件队列
  • 那么来看看执行顺序
void main() {

  Future(() => print("event task"));
  print('start ');
  
  scheduleMicrotask((){
    print('scheduleMicrotask');
  });
  print('end ');
}

  • 可以看到main函数里的 startend先打印了,然后打印了微任务scheduleMicrotask,最后打印了event task
    Future代码的加入队列

Future代码分为构造函数的函数体和(.then/.catchError等)

  • Future构造函数的函数体是放在事件队列中
  • .then的函数体分成几种情况
  1. .then函数体会跟着构造函数体放进事件队列

  2. Future链式调用,.then函数体会依次放入事件队列

  3. Future里或者.then里使用微任务,微任务会在Future完成后才调用

    来分析一个复杂的情况,彻底搞清楚任务执行顺序

void main() {
  print("main start");

  Future(() => print("task1"));

  final future = Future(() => null);

  Future(() => print("task2")).then((_) {
    print("task3");
    scheduleMicrotask(() => print('task4'));
  }).then((_) => print("task5"));

  future.then((_) {
    print('task6');
    Future(() => print('task7')).then((value) {
      Future(() => print('task8'));
    });
  }).then((value) {
    print('task9');
  });
  scheduleMicrotask(() => print('task10'));

  Future(() => print('task11')).then((_) {
    Future(() => print('task12')).then((value) {
      Future(() => print('task13'));
    });
  }).then((_) => print('task14'));

  Future(() => print('task15'));

  print("main end");
}

用下面的图,捋了一下。

从图中可以梳理出打印顺序为:

- `main start`
- `main end`
- `task10`
- `task1`
- `task6`
- `task9`
- `task2`
- `task3`
- `task5`
- `task4`
- `task11`
- `task14`
- `task15`
- `task7`
- `task12`
- `task8`
- `task13`

与实际打印结果一致

isolate
  • 我们知道Dart是单线程的,在Dart中其实有个专门名词叫isolate(隔离)
  • Flutter中就有一个Root isolate, 在Root isolate内部运行一个EventLoop事件循环
  • 用官方的话来说 isolate就是一个隔离的Dart执行上下文
  • Dart是支持多个isolate的,isolate之间不共享任何资源,只能依靠消息机制通信
  • 如果确实有非常耗时的操作,可以自己创建isolate
创建isolate
void main() {

  print(Isolate.current.hashCode);
  Isolate.spawn(testIsolate, "new Isolate");
}
void testIsolate(String msg) {
  print("新的isolate:$msg - ${Isolate.current.hashCode}");
}


通信
void main() {

  print(Isolate.current.hashCode);
  // 创建管道
  ReceivePort receivePort= ReceivePort();

  Future iso = Isolate.spawn(testIsolate, receivePort.sendPort,debugName: '哈哈哈');
  iso.then((value) {
    receivePort.listen((data) {
      print('receiveData:$data');
      //关闭管道
      receivePort.close();
      // 杀死isolate
      value.kill(priority: Isolate.immediate);
    });
  });
}
void testIsolate(SendPort port) {
  print("新的isolate - ${Isolate.current.debugName}  ${Isolate.current.hashCode}");
  port.send("Hello World");
}

compute(Flutter对Isolate的封装,需要在Flutter中使用)
@override
  Widget build(BuildContext context) {
    testCompute();
    return MaterialApp(
        home: Container(),
    );
  }
  void testCompute() async {
    String result = await compute(sendMesg, '哈哈哈');
    print('$result');
  }
  static String sendMesg(String msg) {
    print(msg);
    return '收到了- 回你'+msg;
  }

事件监听

在大前端的开发中,必然存在各种各样和用户交互的情况:比如手指点击、手指滑动、双击、长按等等。

在Flutter中,手势有两个不同的层次:

  • 第一层:原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。
  • 第二层:手势识别(Gesture Detector):这个是在原始事件上的一种封装。
    • 比如我们要监听用户长按,如果自己封装原始事件我们需要监听从用户按下到抬起的时间来判断是否是一次长按事件;
    • 比如我们需要监听用户双击事件,我们需要自己封装监听用户两次按下抬起的时间间隔;
    • 幸运的是各个平台几乎都对它们进行了封装,而Flutter中的手势识别就是对原始指针事件的封装;
    • 包括哪些手势呢?比如点击、双击、长按、拖动等

2.1. 指针事件Pointer

Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:

Pointer的原理是什么呢?

  • 在指针落下时,框架做了一个 hit test 的操作,确定与屏幕发生接触的位置上有哪些Widget以及分发给最内部的组件去响应;

  • 事件会沿着最内部的组件向组件树的根冒泡分发;

  • 并且不存在用于取消或者停止指针事件进一步分发的机制;

原始指针事件使用Listener来监听:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Listener(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onPointerDown: (event) => print("手指按下:$event"),
        onPointerMove: (event) => print("手指移动:$event"),
        onPointerUp: (event) => print("手指抬起:$event"),
      ),
    );
  }
}

image-20190925154947943

2.2. 手势识别Gesture

Gesture是对一系列Pointer的封装,官方建议开发中尽可能使用Gesture,而不是Pointer

建议使用Gesture

建议使用Gesture

Gesture分层非常多的种类:

点击

  • onTapDown:用户发生手指按下的操作
  • onTapUp:用户发生手指抬起的操作
  • onTap:用户点击事件完成
  • onTapCancel:事件按下过程中被取消

双击:

  • onDoubleTap:快速点击了两次

长按:

  • onLongPress:在屏幕上保持了一段时间

纵向拖拽:

  • onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动;
  • onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动;
  • onVerticalDragEnd:指针和屏幕产生接触结束;

横线拖拽:

  • onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动;
  • onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动;
  • onHorizontalDragEnd:指针和屏幕产生接触结束;

移动:

  • onPanStart:指针和屏幕产生接触并可能开始横向移动或者纵向移动。如果设置了 onHorizontalDragStart 或者 onVerticalDragStart,该回调方法会引发崩溃;
  • onPanUpdate:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,该回调方法会引发崩溃。
  • onPanEnd:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了 onHorizontalDragEnd 或者 onVerticalDragEnd,该回调方法会引发崩溃。

从Widget的层面来监听手势,我们需要使用:GestureDetector

  • 当然,我们也可以使用RaisedButton、FlatButton、InkWell等来监听手势
  • globalPosition用于获取相对于屏幕的位置信息
  • localPosition用于获取相对于当前Widget的位置信息
class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("手势测试"),
      ),
      body: GestureDetector(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onTap: () {

        },
        onTapDown: (detail) {
          print(detail.globalPosition);
          print(detail.localPosition);
        },
        onTapUp: (detail) {
          print(detail.globalPosition);
          print(detail.localPosition);
        }
      ),
    );
  }
}


image-20200317172326395

image-20200317172326395

二. 跨组件事件

在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。

其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:

  • EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
  • 这个EventBus我们可以自己实现,也可以使用第三方的EventBus;

这里我们直接选择第三方的EventBus:

dependencies:
  event_bus: ^1.1.1

第一:我们需要定义一个希望在组件之间传递的对象:

  • 我们可以称之为一个时间对象,也可以是我们平时开发中用的模型对象(model)
class UserInfo {
  String nickname;
  int level;
  
  UserInfo(this.nickname, this.level);
}

第二:创建一个全局的EventBus对象

final eventBus = EventBus();

第三:在某个Widget中,发出事件:

class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text("HYButton"),
      onPressed: () {
        final info = UserInfo("why", 18);
        eventBus.fire(info);
      },
    );
  }
}

第四:在某个Widget中,监听事件

class HYText extends StatefulWidget {
  @override
  _HYTextState createState() => _HYTextState();
}

class _HYTextState extends State<HYText> {
  String message = "Hello Coderwhy";

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

    eventBus.on<UserInfo>().listen((data) {
      setState(() {
        message = "${data.nickname}-${data.level}";
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(message, style: TextStyle(fontSize: 30),);
  }
}

最后福利

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值