Flutter入门系列-40分钟快速入门Dart基础(下)

转载:40分钟快速入门Dart基础(下) - 知乎

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

Dart 目录

一、前言:

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

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

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

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

下面我们先来看一张图

preview

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

下面我们来说说单线程模型:

重点:只要 Dart 函数开始执行,它将会执行到这个函数结束,也就是说Dart的函数不会被其他Dart 代码打断。【思考:Java会这样吗?不会的,Java会有线程切换】

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

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);
    });
}

可以看到代码中,我们接收消息使用了 listen 函数来监听消息。假设我们现在在 main 方法最后加入 sleep 休眠,会不会影响 listen 回调的时机?

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);
    });
}

结果是会有延迟等待,然后我们的 listen 才打印出其他 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等等

二、什么是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("我就要和我的过儿回古墓");
    });
  }

三、Stream详解

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

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

void readFile(){
    // 说明:这里的listen 其实就是一个订阅了Stream(调用openRead()返回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 结束
}
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));
}

四、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 执行完成,下一行代码会立即执行。

五、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();
}

六、异常

和 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的资料。

同时也遇到了比较好的博客如下:梁飞宇博客

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值