Flutter-Dart异步和网络请求

叶展
2023-12-01

1.Dart异步

1.1 Dart单线程

Dart是单线程的,不支持多线程。
Dart实现异步是使用的单线程+循环调用的方式
类似于网络请求、文件读写的IO操作,都可以基于非阻塞调用

阻塞式调用和非阻塞式调用

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态
阻塞式调用: 调用结果返回之前,当前线程会被挂起,调用线程只有在得到调用结果之后才会继续执行
非阻塞式调用: 调用执行后,当前线程不会停止执行,只需要过一段时间来检查一下有没有结果返回即可

1.2 Dart事件循环

什么是事件循环

单线程模型中主要就是在维护着一个事件循环(Event Loop)
事件循环是什么:
1.将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中。
2.不断的从时间队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到时间队列清空位置

1.3 Dart异步操作

Dart中的异步操作主要使用Future以及async、await

1.3.1 认识Future

import 'dart:io';

main(List<String> args) {
  print("main start");
  //发送一个网络请求
  var result = getNetworkData();
  print(result);
  print("main end");
}

//模拟一个网络请求
String getNetworkData(){
  sleep(Duration(seconds:2));
  return "Hello World";
}

输出结果

main start
//控制台输出在此处有等待
Hello World
main end

此时网路请求阻塞了线程

加入Future后

import 'dart:io';

main(List<String> args) {
  print("main start");
  //发送一个网络请求
  var future = getNetworkData();
  print(future);

  //2.拿到结果
  //then后面的回调函数什么时候被执行?
  //需要在Future(函数)有结果,才执行下面的回调函数
  future.then((value){
    print(value);
  }).catchError((err){
    print("打出错误:$err");
  }).whenComplete(() => {
    print("代码执行完成")
  });

  print("main end");
}

//模拟一个网络请求
Future getNetworkData(){
  return Future((){
    //1.将耗时操作包裹到Future的回调函数中
    //1.1结果情况1:只要有返回结果,就执行Future对应的then的回调
    //1.2结果情况2:如果没有结果返回(有错误信息),需要在Future回调中抛出一个异常
    sleep(Duration(seconds:2));
    //模拟结果情况1
    return "Hello World";
    //模拟结果情况2
    throw Exception("我是错误信息");
  });
}
main start
Instance of 'Future<dynamic>'
main end
Hello World
代码执行完成

main start
Instance of 'Future<dynamic>'
main end
打出错误:Exception: 我是错误信息
代码执行完成

1.3.2 Future的链式调用

import 'dart:io';

main(List<String> args) {
  print("main start");
  
  Future((){
    //1.发送第一次请求
    sleep(Duration(seconds:8));
    return "第一次的结果";
  }).then((value){
    print(value);
    //2.发送的第二次请求
    sleep(Duration(seconds:3));
    //return "第二次的结果";
    throw Exception("第二次请求异常");
  }).then((value){
    print(value);
  }).catchError((err){
    print(err);
  }).whenComplete(() => {
    print("代码完成")
  });

  print("main end");
}

1.4 await、async

await、async是Dart中的关键字,他们可以让我们用同步的代码格式,去实现异步的调用过程。并且,通常一个async的函数会返回一个Future。

不使用异步时
main(List<String> args) {
  print("main start");

  var result = getNetworkData();
  print(result);

  print("main end");
}

String getNetworkData(){
  sleep(Duration(seconds:3));
  return "Hello World";
}

main start
//程序在这里阻塞,3秒后打印出后面内容
Hello World
main end

使用Future时
main(List<String> args) {
  print("main start");

  var result = getNetworkData().then((value) {
    print(value);
  });
  print(result);

  print("main end");
}

Future getNetworkData(){
  return Future((){
    sleep(Duration(seconds:5));
    return "使用了Future";
  });
}

main start
Instance of 'Future<Null>'
main end
//程序在这里等待5秒后输出结果,不阻塞main end
使用了Future
使用await、async
await、async相当于Future的语法糖
1.await必须在async函数中才能使用
2.async函数返回的结果必须是一个Future

await需要写在耗时操作之前,表示等待该操作结果

main(List<String> args) {
  print("main start");

  var result = getNetworkData().then((value) {
    print(value);
  });
  print(result);

  print("main end");
}

Future getNetworkData() async {
  var response = await (这里写耗时操作);
  return "Hello World";
}

虽然return写的返回String,内部会自动包裹为Future
main start
Instance of 'Future<Null>'
main end
Hello World

链式调用普通写法
main(List<String> args) {
  print("main start");
  getData();
  print("main end");
}

void getData(){
  //1.调用第一次网络请求
  getNetworkData("argument1").then((value){
    print(value);
    return getNetworkData(value);
  }).then((value) {
    print(value);
    return getNetworkData(value);
  }).then((value) {
    print(value);
  });
}

Future getNetworkData(String arg){
  return Future((){
    sleep(Duration(seconds:3));
    return "Hello World "+arg;
  });
}

main start
main end
Hello World argument1
Hello World Hello World argument1
Hello World Hello World Hello World argument1
使用await、async进行链式调用
main(List<String> args) {
  print("main start");
  getData().then((value){
    print("最终结果$value");
  }).catchError((err){
    print(err);
  });
  print("main end");
}

Future getData() async{
  //1.调用第一次网络请求
  var res1 = await getNetworkData("argument1");
  print(res1);
  var res2 = await getNetworkData(res1);
  print(res2);
  var res3 = await getNetworkData(res2);
  print(res3);
  return res3;
}

Future getNetworkData(String arg){
  return Future((){
    sleep(Duration(seconds:3));
    return "Hello World "+arg;
  });
}

1.5 多核CPU的利用-isolate

1.5.1 什么是isolate

  • 我们都知道Dart是单线程的,这个线程有自己可以访问的内存空间以及运行的事件循环
  • 我们可以将这个空间系统称之为是一个isolate
  • 在 Isolate 中,资源隔离做得非常好,每个 Isolate 都有自己的 Event Loop 与 Queue,Isolate 之间不共享任何资源,只能依靠消息机制通信,因此也就没有资源抢占问题。
  • 但是,如果只有一个Isolate,那么意味着我们只能永远利用一个线程,这对于多核CPU来说,是一种资源的浪费。
  • 如果在开发中,我们有非常多耗时的计算,完全可以自己创建Isolate,在独立的Isolate中完成想要的计算操作。(一般不建议使用Isolate,大数据尽量在服务器端处理)
//第一个参数是方法的名称
//第二个参数是给方法传的参数
import "dart:isolate";

main(List<String> args) {
  Isolate.spawn(foo, "Hello Isolate");
}

void foo(info) {
  print("新的isolate:$info");
}

新的isolate:Hello Isolate

1.5.2 Isolate通信机制

但是在真实开发中,我们不会只是简单的开启一个新的Isolate,而不关心它的运行结果:

  • 我们需要新的Isolate进行计算,并且将计算结果告知Main Isolate(也就是默认开启的Isolate);
  • Isolate 通过发送管道(SendPort)实现消息通信机制;
  • 我们可以在启动并发Isolate时将Main Isolate的发送管道作为参数传递给它;
  • 并发在执行完毕时,可以利用这个管道给Main Isolate发送消息;
import "dart:isolate";

main(List<String> args) async {
  // 1.创建管道
  ReceivePort receivePort= ReceivePort();

  // 2.创建新的Isolate
  Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);

  // 3.监听管道消息
  receivePort.listen((data) {
    print('Data:$data');
    // 不再使用时,我们会关闭管道
    receivePort.close();
    // 需要将isolate杀死
    isolate?.kill(priority: Isolate.immediate);
  });
}

void foo(SendPort sendPort) {
  sendPort.send("Hello World");
}

但是我们上面的通信变成了单向通信,如果需要双向通信呢?

  • 事实上双向通信的代码会比较麻烦
  • Flutter提供了支持并发计算的compute函数,它内部封装了Isolate的创建和双向通信;
  • 利用它我们可以充分利用多核心CPU,并且使用起来也非常简单;
  • 注意:下面的代码不是dart的API,而是Flutter的API,所以只有在Flutter项目中才能运行
main(List<String> args) async {
  int result = await compute(powerNum, 5);
  print(result);
}

int powerNum(int num) {
  return num * num;
}

2. Flutter网络请求

在Flutter中常见的网络请求方式有三种:HttpClient、http库、dio库

2.1 HttpClient

HttpClient是dart自带的请求类,在io包中,实现了基本的网络请求相关的操作。
网络调用通常遵循如下课步骤:

  1. 创建client
  2. 构造Uri
  3. 发起请求,等待请求,同时可配置请求headers、body
  4. 关闭请求,等待响应
  5. 解码响应的内容
  void requestNetwork() async{
    //1.创建HttpClient对象
    final httpClient = HttpClient();
    //2.构建请求的uri
    final uri = Uri.parse("https://wanandroid.com/wxarticle/chapters/json");
    //3.构建请求
    final request = await httpClient.getUrl(uri);
    //4.发送请求,必须
    final response = await request.close();
    if(response.statusCode == HttpStatus.ok){
      print(await response.transform(utf8.decoder).join());
    }else{
      print(response.statusCode);
    }
  }
  • HttpClient也可以发送post相关的请求
  • HttpClient虽然可以发送正常的网络请求,但是会暴露过多的细节:比如需要主动关闭request请求,拿到数据后也需要手动的进行字符串解码
  • 实际开发中,通常使用一些库来完成网络请求

2.2 http库

http是Dart官方提供的另一个网络请求类,相比于HttpClient,易用性提升了。但是,没有默认集成到Dart的SDK中,所以我们需要先在pubspec.yaml中依赖它

http:^0.12.0+2
import 'package:http/http.dart' as http;

 void requestNetwork() async{
    //1.创建Client
    final client = http.Client();
    //2.构建uri
    final url = Uri.parse("https://wanandroid.com/wxarticle/chapters/json");
    //3.发送请求
    final response = await client.get(url);
    //4.获取结果
    if(response.statusCode == HttpStatus.ok) {
      print(response.body);
    }else{
      print(response.statusCode);
    }
  }

2.3 dio第三方库

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配等

dio: ^3.0.10
 类似资料: