40分钟快速入门Dart基础(下)

本章是对Dart 基础讲解的最后一章:咱们讲解余下来的异步、泛型、异常,相对来讲余下来的这三章稍微有点难度,可是小伙伴只要用心跟着我一块儿学习,应该问题不,若是有问题也能够在下方留言或者私信我。好废话很少说直接进入如主题:markdown

Dart目录网络

1、前言:

对于已经叱咤开发武林已久的开发人员来说,异步是一个很深的知识点。而咱们在接触的每种语言里面基本上都会提到异步,一样地在使用Dart开发项目过程当中,也是会有异步操做的。app

咱们在Java , oc中可使用线程来实现异步操做,可是Dart是单线程模型,想要实现异步操做的话,咱们可使用事件队列来处理。异步

重点:Dart是单线程模型语言,也就没有了所谓的主线程/子线程之分。因此相对来讲Dart线程理解起来和学习起来是相对容易的。async

再这里首咱们要想理解Dart单线程模型,首先咱们理解什么是Even-Looper,Event-Queueide

下面咱们先来看一张图函数

从上图咱们能够看出:Event Loop就是从EventQueue中获取Event、处理Event一直到EventQueue为空为止,而EventQueue小伙伴们能够看做成一个管道,Events 就是管道中每个事件。可能说事件你们仍是有点模糊那咱们直接说是:用户的输入、文件IO、网络请求、按钮的点击这些均可以说成Event。oop

下面咱们来讲说单线程模型:性能

重点:只要Dart函数开始执行,它将会执行到这个函数结束,也就是说Dart的函数不会被其余Dart代码打断。学习

因此在Dart中引入一个关键词名叫:isolate 那什么叫:isolate 首先从字面上来看是”隔离“每一个isolate都是隔离的,并不会共享内存。isolate是经过在通道上传递消息来通讯,这就标识了Dart从性能上大大全部提高。不像其它语言(包括Java、Kotlin、Objective-C和Swift)都使用抢占式来切换线程。每一个线程都被分配一个时间分片来执行,若是超过了分配的时间,线程将被上下文切换抢占。可是若是在线程间共享的资源(如内存)正在更新时发生抢占,则会致使竞态条件。 Flutter 对Dart情有独钟的那些事儿

import 'dart:core';
import 'dart:isolate';

int i;

void main() {
    i = 10;
    //建立一个消息接收器
    ReceivePort receivePort = new ReceivePort();
    //建立isolate
    Isolate.spawn(isolateMain, receivePort.sendPort);

    //接收其余isolate发过来的消息
    receivePort.listen((message) {
        //发过来sendPort,则主isolate也能够向建立的isolate发送消息
        if (message is SendPort) {
            message.send("好呀好呀!");
        } else {
            print("接到子isolate消息:" + message);
        }
    });
}

/// 新isolate的入口函数
void isolateMain(SendPort sendPort) {
    // isolate是内存隔离的,i的值是在主isolate定义的因此这里得到null
    print(i);

    ReceivePort receivePort = new ReceivePort();
    sendPort.send(receivePort.sendPort);


    // 向主isolate发送消息
    sendPort.send("去大保健吗?");


    receivePort.listen((message) {
        print("接到主isolate消息:" + message);
    });
}
复制代码

能够看到代码中,咱们接收消息使用了listene函数来监听消息。假设咱们如今在main方法最后加入sleep休眠,会不会影响listene回调的时机?

import 'dart:io';
import 'dart:isolate';

int i;

void main() {
    i = 10;
    //建立一个消息接收器
    ReceivePort receivePort = new ReceivePort();
    //建立isolate
    Isolate.spawn(isolateMain, receivePort.sendPort);

    //接收其余isolate发过来的消息
    receivePort.listen((message) {
        //发过来sendPort,则主isolate也能够向建立的isolate发送消息
        if (message is SendPort) {
            message.send("好呀好呀!");
        } else {
            print("接到子isolate消息:" + message);
        }
    });

    //增长休眠,是否会影响listen的时机?
    sleep(Duration(seconds: 2));
    print("休眠完成");
}

/// 新isolate的入口函数
void isolateMain(SendPort sendPort) {
    // isolate是内存隔离的,i的值是在主isolate定义的因此这里得到null
    print(i);

    ReceivePort receivePort = new ReceivePort();
    sendPort.send(receivePort.sendPort);
    // 向主isolate发送消息
    sendPort.send("去大保健吗?");


    receivePort.listen((message) {
        print("接到主isolate消息:" + message);
    });
}
复制代码

结果是会有延迟等待,而后咱们的listene才打印出其余isolate发过来的消息。到这个地方可能开发过Android 小伙伴会以为如同Android Handler同样。

在Dart运行环境中也是靠事件驱动的,经过event loop不停的从队列中获取消息或者事件来驱动整个应用的运行,isolate发过来的消息就是经过loop处理。可是不一样的是在Android中每一个线程只有一个Looper所对应的MessageQueue,而Dart中有两个队列,一个叫作event queue(事件队列),另外一个叫作microtask queue(微任务队列)。

这里有个疑问:其实Dart中的Main Isolate只有一个Event Looper。可是Dart中为啥存在两个列队。

那microtask queue 存在的意义是啥:

其实这个里面有巧妙的设计:microtask queue 存在是但愿经过这个Queue来处理稍晚一些的事情,可是在下一个消息到来以前须要处理完的事情。当Event Looper正在处理Microtask Queue中的Event时候,Event Queue中的Event就中止了处理了,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等等

2、什么是Future

在 Flutter 中有两种处理异步操做的方式Future和Stream,Future用于处理单个异步操做,Stream用来处理连续的异步操做。啥意思呢?就比如:杨过练武功秘籍:练了打狗棒,并将打狗棒经过一段时间练会了这就是一个Future。

下面咱们先看看单个异步处理Future,其实在 Dart 库中随处可见 Future 对象:以下图:

一般操做一个异步函数,并对其设置返回的对象,而这个对象就是一个 Future。 当一个 future 执行完后,他里面的值 就可使用了,可使用 then() 来在 future 完成的时候执行其余代码。Future对象其实就表明了在事件队列中的一个事件的结果。

///读取文件
  void readStringFromFile() {
    File("/Users/Test/1.txt").readAsString().then((content) {
      //任务执行完成会进入这里,可以得到返回的执行结果。
      print(content);
    }).whenComplete(() {
      //当任务中止时,最后会执行这里。
      print("杨过是武林高手");
    }).catchError((e, s) {
      //若是文件地址时会发生异常,这时候能够利用catchError捕获此异常。
      print(s);
    });
  }

复制代码
void moreTaskZips() {
    //能够等待多个异步任务执行完成后,再调用 then()。
    //只有有一个执行失败,就会进入 catchError()。
    Future.wait([
      // Future.delayed() 延迟执行一个延时任务。
      // 2秒后返回结果
      Future.delayed(new Duration(seconds: 2), () {
        return "杨过";
      }),
      // 4秒后返回结果
      Future.delayed(new Duration(seconds: 4), () {
        return "我喜欢小龙女";
      })
    ]).then((v) {
      //执行成功会走到这里
      print(v[0] + v[1]);
    }).catchError((v) {
      //执行失败会走到这里
      print("我是尹志平");
    }).whenComplete(() {
      //不管成功或失败都会走到这里
      print("我就要和个人过儿回古墓");
    });
  }

复制代码

3、Stream详解

Stream是一个抽象类,用于表示一序列异步数据的源。它是一种产生连续事件的方式,能够生成数据事件或者错误事件,以及流结束时的完成事件。

Stream 的好处是处理过程当中内存占用较小。举个例子:在读取file文件数据的时候, Future 只能一次获取异步数据。而 Stream 能屡次异步得到的数据。若是当文件比较大,明显Futrue占用的时间更久,这样子就会导内存占用过大。

void readFile(){
    // 说明:这里的listen 其实就是一个订阅了Stream 咱们经过查看源码发现会返回一个 StreamSubscription 订阅者
     File("/Users/Test/app-release.apk").openRead().listen((List<int> bytes) {
      print("Stream我被执行"); //执行屡次
    });

     //经过查询源码:readAsBytes 返回的是一个Future
     File("/Users/Test/app-release.apk").readAsBytes().then((_){
      print("future我被执行"); //执行1次
    });
  }

复制代码

Stream 可经过listen进行数据监听(listen其实就是订阅当前Stream,会返回一个StreamSubscription订阅者,订阅者提供了取消订阅的cancel(),去掉后咱们的listen中就接不到任何信息了。除了cancel()取消方法以外还有pause()暂停),经过error接收失败状态,经过done来接收结束状态;

怎么建立Stream和操做Stream流数据?

Dart中提供了多种建立Stream方法:

void main() {
// 第一种:建立方法Stream.fromFuture(Future<T> future)
  _createStreamFromFuture();
}

_createStreamFromFuture() {
  Future<String> getTimeOne() async {
    await Future.delayed(Duration(seconds: 3));
    return '当前时间为:${DateTime.now()}';
  }
  

  Stream.fromFuture(getTimeOne())
      .listen((event) => print('测试经过Stream.fromFuture建立Stream -> $event'))
      .onDone(() => print('测试经过Stream.fromFuture建立Stream -> done 结束'));

  //输出结果
  //测试经过Stream.fromFuture建立Stream -> 当前时间为:2020-07-20 12:01:40.280591
  //测试经过Stream.fromFuture建立Stream -> done 结束
}

复制代码
void main() {
// 第二种建立方法: Stream.fromFutures(Iterable<Future<T>> futures)
  _createStreamFromFuture();
}

_createStreamFromFuture() {
  Future<String> getTimeOne() async {
    await Future.delayed(Duration(seconds: 3));
    return '当前时间为:${DateTime.now()}';
  }


  Future<String> getTimeTwo() async {
    await Future.delayed(Duration(seconds: 3));
    return '当前时间为:${DateTime.now()}';
  }


  Stream.fromFutures([getTimeOne(),getTimeTwo()])
      .listen((event) => print('测试经过Stream.fromFutures建立Stream -> $event'))
      .onDone(() => print('测试经过Stream.fromFutures建立Stream -> done 结束'));

  //输出结果
  //测试经过Stream.fromFuture建立Stream -> 当前时间为:2020-07-20 12:01:40.280591
  //测试经过Stream.fromFuture建立Stream -> done 结束
}

复制代码

Stream提供的:fromFutures里面能够塞多个Future, 经过一系列的 Future 建立新的单订阅流,每一个 Future 都会有自身的 data / error 事件,当这一系列的 Future 均完成时,Stream 以 done 事件结束;若 Futures 为空,实则是没有意义的。则 Stream 会马上关闭。

void main() {
// 第三种建立方法: Stream.fromIterable(Iterable<T> elements)
  _createStreamFromFuture();
}

_createStreamFromFuture() {
  var data = ['黄药师', '郭靖', '杨过', false];

  Stream.fromIterable(data)
      .listen((event) => print('测试经过Stream.fromFuture建立Stream -> $event'))
      .onDone(() => print('测试经过Stream.fromFuture建立Stream -> done 结束'));

// 测试经过Stream.fromFuture建立Stream -> 黄药师
// 测试经过Stream.fromFuture建立Stream -> 郭靖
// 测试经过Stream.fromFuture建立Stream -> 杨过
// 测试经过Stream.fromFuture建立Stream -> false
// 测试经过Stream.fromFuture建立Stream -> done 结束
}

复制代码

stream 广播模式:

Stream有两种订阅模式:单订阅和多订阅。单订阅就是只能有一个订阅者,上面的使用咱们都是单订阅模式,而广播是能够有多个订阅者。经过 Stream.asBroadcastStream() 能够将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性能够判断当前 Stream 所处的模式

import 'dart:async';

import 'dart:io';

void main() {
  _createStreamBroadcast();
}

_createStreamBroadcast() {
  var stream = new File("/Users/Test/app-release.apk").openRead();
  stream.listen((event) => print(' $event'));
  //因为是单订阅,因此这个地方只能有一个,因此下面这种写法是错误
  //stream.listen((event) => print(' 我再添加一个订阅$event'));

  //一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream可使用Stream.asBroadcastStream
  var broadcastStream =
      new File("/Users/Test/app-release.apk").openRead().asBroadcastStream();
  broadcastStream.listen((_) {
    print("我是黄药师1");
  });
  broadcastStream.listen((_) {
    print("我是黄药师2");
  });
}

复制代码

这里咱们要注意一下多订阅模式若是没有及时添加订阅者则可能丢数据。

import 'dart:async';

import 'dart:io';

void main() {
  _createStreamBroadcast();
}

_createStreamBroadcast() {
  //默认是单订阅
  var stream = Stream.fromIterable(["黄药师", "郭靖", "杨过"]);
  //3s后添加订阅者 不会丢失数据
  Timer(Duration(seconds: 3), () => stream.listen(print));

  //建立一个流管理器 对一个stream进行管理
  var streamController = StreamController.broadcast();
  ///我再这个地方添加一个流数据
  streamController.add("小龙女");
  ///先发出事件再订阅 没法接到通知
  streamController.stream.listen((i) {
    print("broadcast:$i");
  });
  //记得关闭
  streamController.close();

  //这里没有丢失,由于stream经过asBroadcastStream转为了多订阅,可是本质是单订阅流,并不改变原始 stream 的实现特性
  var broadcastStream =
      Stream.fromIterable(["黄药师-1", "郭靖-2", "杨过-3"]).asBroadcastStream();
  Timer(Duration(seconds: 3), () => broadcastStream.listen(print));
}

复制代码

4、async/await

使用async和await的代码是异步的,可是看起来很像同步代码。有了这两个关键字,咱们能够更简洁的编写异步代码,而不须要调用Future相关的API

import 'dart:async';

import 'dart:io';

void main() {
   _readData().then((v){
    print("你的名字$v");//输出:你的名字[黄药师, 郭靖, 杨过]
  });
}

List<String> _testAsyncAndAwait() {
  return ["黄药师", "郭靖", "杨过"];
}

_readData() async {
  return  _testAsyncAndAwait();
}


复制代码
  • await关键字必须在async函数内部使用,也就是加await不加async会报错。

  • 调用async函数必须使用await关键字,若是加async不加await会顺序执行代码以下代码:
import 'dart:async';

import 'dart:io';

void main() {
  _startMethod();
  _methodC();
}

_startMethod() async {
  _methodA();
  await _methodB();
  print("start结束");
}

_methodA() {
  print("A开始执行这个方法~");
}

_methodB() async {
  print("B开始执行这个方法~");
  await print("后面执行这句话~");
  print("继续执行这句哈11111~");
}

_methodC() {
  print("我是黄药师!!!");
}

//A开始执行这个方法~
//B开始执行这个方法~
//后面执行这句话~
//我是黄药师!!!
//继续执行这句哈11111~
//start结束

复制代码
  • 当使用async做为方法名后缀声明时,说明这个方法的返回值是一个Future;
  • 当执行到该方法代码用await关键字标注时,会暂停该方法其余部分执行;
  • 当await关键字引用的Future执行完成,下一行代码会当即执行。

5、Dart中泛型

Dart是一种可选的类型语言,因此Dart像其余语言同样也支持泛型,泛型的做用就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)。更直接的理解是传入什么,返回什么,同时支持类型校验。

语法以下:

Collection_name <data_type> identifier= new Collection_name<data_type>
复制代码

下面咱们来聊聊,Dart中泛型方法,泛型类,泛型接口

泛型方法

import 'dart:async';

import 'dart:io';

void main() {
  print(_setUser(User().name)); //输出:黄药师
}

_setUser<T>(T user) {
  return user;
}

class User {
  var name = "黄药师";
}

复制代码

泛型类

import 'dart:async';

import 'dart:io';

void main() {
  User<String>()
    ..addName("黄药师")
    ..addName("杨过")
    ..addName("小龙女")
    ..addName("黄蓉")
    ..printInfo();
}

class User<T> {
  List<T> names = List<T>();

  void addName(T name) {
    names.add(name);
  }

  void printInfo() {
    names.forEach((v) {
      print("我是:$v");
      //输出 我是:黄药师
      //我是:杨过
      //我是:小龙女
      //我是:黄蓉
    });
  }
}

复制代码

泛型接口

import 'dart:async';

import 'dart:io';

void main() {
  Student<String>()
    ..addName("黄药师")
    ..addName("小龙女")
    ..printInfo();
}

class Student<T> implements User<T> {
  List<T> names = List<T>();

  @override
  void addName(T name) {
    names.add(name);
  }

  @override
  void printInfo() {
    names.forEach((v) {
      print("你是:$v");

      //你是:黄药师
      //你是:小龙女
    });
  }
}

abstract class User<T> {
  void addName(T name);

  void printInfo();
}

复制代码

6、异常

和 Java 不一样的是,全部的 Dart 异常是非检查异常。 方法不必定声明了他们所抛出的异常, 而且不要求你捕获任何异常。

Dart 提供了 Exception和Error 类型, 以及一些子类型。你还 能够定义本身的异常类型。可是, Dart 代码能够 抛出任何非 null 对象为异常,不只仅是实现了 Exception 或者Error 的对象。

throw new Exception('这是一个异常');
throw '这是一个异常';
throw 123;
复制代码

与Java不一样之处在于捕获异常部分,Dart中捕获异常一样是使用catch语句,可是Dart中的catch没法指定异常类型。须要结合on来使用,基本语法以下:

try {
	throw 123;
} on int catch(e){
     //使用 on 指定捕获int类型的异常对象 
} catch(e,s){
     //函数 catch() 能够带有一个或者两个参数, 
     //第一个参数为抛出的异常对象,
     //第二个为堆栈信息 ( StackTrace 对象)
    rethrow; //使用 `rethrow` 关键字能够 把捕获的异常给 从新抛出
} finally{
     //finally内部的语句,不管是否有异常,都会执行。
   print("this is finally");
}

复制代码
  • on能够捕获到某一类的异常,可是获取不到异常对象;
  • catch能够捕获到异常对象。这个两个关键字能够组合使用。
  • rethrow能够从新抛出捕获的异常。

总结: 历经三周终于把这三篇文章完成,时间上拉距有点长,文章中针对Dar讲解t相对简单。好比:第一章里面讲解的final、const知识点,和本章异步讲解的都不够细,后期会出两篇单独针对“Dart异常”和final、const知识的讲解。

最后经过在写三篇文章的同时查询了须要关于Flutter的资料。同时也遇到了比较好的博客以下:梁飞宇博客

本人知乎 如何快速掌握Dart这门语言并进阶Flutter变成大神

最后感谢小伙伴认真阅读《40分钟快速入门Dart基础》三篇文章,若是有喜欢能够点赞关注,也能够私信本人,后期我会持续输出关于Flutter知识和一些开发的技巧。

相关文章
相关标签/搜索