三、Flutter-Flutter进阶

第九部分 异步操作与网络封装

1、异步操作

如何吃力耗时的操作呢?

  • 针对如何处理耗时操作, 不同的操作语言有不同的处理方式
  • 处理方式一: 多线程, 比如Java、C++, 我们普遍的做法是开启一个新的线程(Thread), 在新的线程中完成这些异步操作, 再通过线程间的通讯方式, 将拿来的数据传递给主线程
  • 处理方式二: 单线程+事件循环, 比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理.

什么是Dart事件循环?

单线程模型中主要就是维护着一个事件循环(Event Loop)

  • 事实上事件循环并不复杂, 它就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列中(Event Queue)
  • 不断的从事件队列中取出事件, 并执行对应需要执行的代码块, 直到事件队列清空为止.
// 这里我们使用数组模拟队列, 先进先出的原则
List eventQueue = [];
var event;

// 时间循环从启动的一刻, 永远在执行
while(true){
    if(eventQueue.length > 0){
    // 取出一个事件
    event = eventQueue.removeAt(0);
    // 执行该事件
    event();
  }
}

2、Dart的异步操作-Future

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

如果你之前有过前端的ES6、ES7编程经验, 那么完全可以将Future理解成Promise, async、await和ES7中基本一致.

import 'dart:io'
main(List<String> args){
  print("main start");
  
  // 发送一个网络请求
  var future = getNetworkData();
  print(future);
  //sp2.拿到结果
  // then后面的回调函数需要在Future(函数)有结果时才执行
  future.then((dynamic value){
    print(value);
  }).catchError((err){//捕获异常
    print("执行catchError代码:$err -------");
  }).whenComplete((){
    print("代码执行完成");
  });
  
  print("main end");
}

// 模拟一个网络请求
Future getNetworkData(){
  //sp1.将耗时操作包裹到Future的回调函数中
  // 1> 只要有返回结果, 那么就执行Future对应的then的回调(Promise-resolve)
  // 2> 如果没有结果返回(有错误信息), 需要在Future的回调中抛出异常(Promise-reject)
    return Future<String>(() {
      sleep(Duration(seconds: 5));
    //return "Hello World"; //1>
    throw Exception("我是错误信息");//2>
  });
}

2.1、Future链式调用:

main(List<String> args){
  print("main start");
  
  Future((){
    //1.发送的第一次请求
    sleep(Duration(seconds: 3));
//    return "第一次的结果";
    throw Exception("第一次异常")
  }).then((dynamic value){
    print(value);
    //2. 发送的第二次请求
    sleep(Duration(seconds: 2));
    return "第二次的结果";
  }).then((res){
    print(res);
    //3.发送第三次请求
    sleep(Duration(seconds: 1));
    return "第三次的结果";
  }).then((res){
    print(res);
  }).catchError((err){
    print("执行catchError代码:$err -------");
  });
  
  print("main end");
}

2.2、Future其他API

//直接接收消息
Future.value("哈哈哈").then((res){
  print(res);
});

//直接捕获错误信息
Future.error("错误信息").catchError((err){
  print(err);
});

//延迟执行
Future.delayed(Duration(seconds: 3), (){
  return "Hello Flutter"
}).then((res){
  reutrn "Hello World";
}).then((res){
  print(res);
});

3、Dart的异步操作-await、async

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

3.1、await、async的使用

import 'dart:io'
main(List<String> args){
  print("main start");
  
  // 发送一个网络请求
  var future = getNetworkData();
  print(future);
  future.then((dynamic value){
    print(value);
  }).catchError((err){
    print("执行catchError代码:$err -------");
  }).whenComplete((){
    print("代码执行完成");
  });
  
  print("main end");
}

/*
解决两个问题:
1.await必须在async函数中才能使用
2.async函数返回的结果必须是一个Future
*/
Future getNetworkData() async{
    await sleep(Duration(seconds: 5));
    //return "Hello World"; //使用了async、await后返回值, 系统会自动包裹一层Future
    throw Exception("我是错误信息");//2>
}

3.2、await、async的练习

//需求: 多次网络请求,第二次结果作为下一次的请求参数
import 'dart:io'
main(List<String> args){
  print("main start");
  
  // getData();
  
  getData2().then((res){
    
  }).catchError((err){
    
  });
  
  print("main end");
}

/*
void getData(){
  //第一次请求
  getNetworkData("argument1").then((res){
    print(res);
    return getNetworkData(res);//第二次请求
  }).then(res){
    print(res);
    return getNetworkData(res);//第三次请求
  }).then(res){
    print(res);
  });
}
*/
//😁对👆上面简写:
Future getData2() async{
    var res1 = await getNetworkData("argument1");
  var res2 = await getNetworkData(res1);
  var res3 = await getNetworkData(res2);
  return res3;
}

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

//同时创建多个Future是并发调用

文档: https://mp.weixin.qq.com/s/Ygs-sOtrohNf4vGpZcrnYw

4、多核CPU的利用

4.1、isolate的理解

  • 我们已经知道Dart是单线程的, 这个线程有自己可以访问的内存空间以及需要运行的事件循环;
  • 我们可以将这个空间系统称为一个isolate;
  • 比如Flutter中就有一个Root isolate, 负责运行Flutter的代码, 比如UI渲染、用户交互等等;

在isolate中, 资源隔离做的非常好, 每个isolate都有自己的Event Loop 与 Queue,

  • isolate之间不共享任何资源, 只能依靠消息机制通信, 因此也就没有资源抢占问题.

4.2、isolate的使用

import 'dart:isolate';
main(List<String> args){
  print("main start");
  
  // 创建isolate
  // 第一个参数: 要执行的函数
  // 第二个参数: 要执行的函数的参数
  Isolate.spawn(calc, 100);
  
  print("main end");
}

void calc(int count){
  var total = 0;
  for(var i = 0; i < count; i++){
    total += i;
  }
  print(total);
}

4.2、isolate之间的通讯

/// ❌不推荐: 写起来比较麻烦
import 'dart:isolate';
main(List<String> args) async{
  print("main start");
  
  // 1.创建管道
  ReceivePort receivePort = ReceivePort();
  
  //2.创建Isolate
  Isolate isolate = await Isolate.spawn<SendPort>(foo, receiverPort.sendPort);
  
  //3.监听管道
  receivePort.listen((message){
    print(message);
    receivePort.close();
    isolate.kill();
  });
  
  print("main end");
}

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

事实上双向通讯的代码会比较麻烦:

  • Flutter提供了支持并发计算的 compute函数, 它内部封装了Isolate的创建和双向通信;
  • 利用它我们可以充分利用多核心CPU, 并且使用起来也非常简单
/// ✅推荐: 写起来比较简单
main(List<String> args) async{
  print("main start");
  
  //compute里接收的函数参数, 要求必须是全局函数
  var result = await compute(calc, 100);
  print("-----: $result");
  
  print("main end");
}

void calc(int count){
  var total = 0;
  for(var i = 0; i < count; i++){
    total += i;
  }
  print(total);
}

Dart是单线程的, Flutter是多线程的

5、网络请求

5.1、网络请求的方式

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

5.2、dio库

类似于前端的 axios

main(List<String> args) async{
  //发送网络请求
  //1.创建Dio对象
  final dio  = Dio();
  
  //2.发送网络请求
  dio.get("https://httpbin.org/get").then((res){
    print(res);
  });
  
  dio.post("https://httpbin.org/get").then((res){
    print(res);
  });
}
  • 1.参数-拦截器(封装)
  • 2.在开发中只要用到第三方库, 建议大家都进行一层封装

5.3、网络库的封装

新建service目录-->新建http_request.dart文件:

class HttpRequest {
  static final BaseOption baseOptions = BaseOption(baseUrl: HttpConfig.baseURL, connectTimeout: HttpConfig.timeout);
  static final Dio dio = Dio(baseOptions);
  
  static Future<T> request<T>(String url, 
    {String method = "get" , //请求方式
    Map<String, dynamic> params,//请求参数
    Interceptor inter//拦截器
    }) async{
    //1.创建单独配置
    final options = Options(method: method);
    
    // 全局拦截器
    // 创建默认的全局拦截器
    Interceptor dIner = InterceptorsWrapper(
        onRequest: (options){
        print("请求拦截");
        return options;
      },
      onResponse: (response){
        print("响应拦截");
        return response;
      },
      onError: (err) {
        print("错误拦截");
        return err;
      }
    );
    List<Interceptor> inters = [dIner];
    
    // 如果用户没有传拦截器, 就用将默认拦截器添加
    if(inter != null){
      inters.add(inter);
    }
    
    //统一添加到拦截器中
    dio.interceptors.addAll(inters);
    
    //2.发送网络请求
    try {
      Response response = await dio.request(url, queryParameters: params, options: options);
      return response.data;
    } on DioError catch(e){
      return Future.error(e);
    }
    
    return response.data;
  }
}

新建service目录-->新建config.dart文件:

class HttpConfig {
  static const String baseURL = "https://httpbin.org";
  static const int timeout = 5000;
}

使用封装:

HttpRequest.request("/get", params: {"name":"why"}).then((res){
  print(res);
}).catchError((err){
  
});

第十部分 状态共享

1.1. InheritedWidget

InheritedWidget和React中的context功能类似,可以实现跨组件数据的传递。

定义一个共享数据的InheritedWidget,需要继承自InheritedWidget

  • 这里定义了一个of方法,该方法通过context开始去查找祖先的HYDataWidget(可以查看源码查找过程)
  • updateShouldNotify方法是对比新旧HYDataWidget,是否需要对更新相关依赖的Widget
//继承自InheritedWidget
class HYDataWidget extends InheritedWidget {
  final int counter;//1.定义共享状态

  //2.定义构造方法
  HYDataWidget({this.counter, Widget child}): super(child: child);

  // 3.通过静态方法获取组件最近的当前InheritedWidget
  static HYDataWidget of(BuildContext context) {
    // 沿着Element树, 去找到最近的HYDataElement, 从Element中取出counter对象
    return context.dependOnInheritedWidgetOfExactType();
  }

  //4.决定要不要回调didChangeDependencies方法
  //必须实现该抽象类InheritedWidget的方法. 
  //如果返回true: 执行依赖当前的InheritedWidget的State中的didChangeDependencies
  @override
  bool updateShouldNotify(HYDataWidget oldWidget) {
    return this.counter != oldWidget.counter;
  }
}

创建HYDataWidget,并且传入数据(这里点击按钮会修改数据,并且重新build)

class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {  
  int data = 100;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedWidget"),
      ),
      body: HYDataWidget(//设置为父组件, 为了实现状态共享
        counter: data,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              HYShowData01(),
              HYShowData02()
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            data++;
          });
        },
      ),
    );
  }
}

class HYShowData01 extentds StatefulWidget {
  @override
  _HYShowDate01State createState() => _HYShowData01State();
}

class _HYShowData01State extends State<HYShowData01>{
  @override
  void didChangeDependencies(){
    super.didChangeDependencies();
    print("执行了_HYShowData01State中的didChangeDependencies")
  }
  @override
  Widget build(BuildContext context){
    int counter = HYCounterWidget.of(content).counter; //获取共享状态
    
    return Card(
        color: Colors.red,
      child: Text("当前计数: $counter"),
    );
  }
}

class HYShowData02 extentds StatelessWidget{
  @override
  Widget build(BuildContext context){
    int counter = HYCounterWidget.of(content).counter;//获取共享状态
    
    return Card(
        color: Colors.red,
      child: Text("当前计数: $counter", style: TextStyle(fontSize: 30)),
    );
  }
}

在某个Widget中使用共享的数据,并且监听

2.1. Provider的基础用法

⚠️AS快捷键:

  • Control + n: 快速生成getter、setter、toString、Constructor

在counter_view_model中:

//1.创建自己需要共享的数据
class HYCounterViewModel extends ChangeNotifier{
  late int _counter;

  // 使用control + n 快捷键, 快速实现_counter的set和get方法
  int get counter => _counter;
  set counter(int value) {
    _counter = value;
    //值发生改变时, 通知刷新
    notifyListeners();
  }
}

在main.dart中:

// 2.在引用程序的顶层ChangeNotifierProvider
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (ctx) => HYCounterViewModel(),
      child: const MyApp(),
    )
  );
}

在其他位置使用共享的数据:

// 3.在其他位置使用共享数据
class HYShowData01 extentds StatelessWidget{
  @override
  Widget build(BuildContext context){
    int counter = Provider.of<HYCounterViewModel>(context).counter;//获取共享状态,方式一
    print("data01的build方法")//执行
    return Card(
        color: Colors.red,
      child: Text("当前计数: $counter", style: TextStyle(fontSize: 30)),
    );
  }
}

class HYShowData02 extentds StatelessWidget{
  @override
  Widget build(BuildContext context){
    print("data02的build方法")//不执行
    return Card(
        color: Colors.red,
      child: Consumer<HYCounterViewModel>(//获取共享状态,方式二(推荐使用)✅
        build: (ctx, counterVM, child){
          return Text("当前计数: ${counterVM.counter}", style: TextStyle(fontSize: 30));
        }
      )
    );
  }
}

/*Provider.of 与 Consumer的区别:
    Provider.of: 当Provider中的数据发生改变时, Provider.of所在的Widget整个build方法都会重新构建;
    Consumer(相对推荐): 当Provider中的数据发生改变时,只会重新执行Consumer的builder
*/

在其他位置修改共享的数据:

//4.修改VM里面共享数据的值
floatingActionButton: Consumer<HYCounterViewModel>(
    builder: (ctx, counterVM, child){
    print("floatingActionButton build方法被执行")//执行, 需要优化: 这里build方法不需要重新构建
    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () {
        setState(() {
          counterVM.counter += 1;
        });
      },
    ),
  }
)
  
⚠️优化方式一: Icon不需要重新构建
floatingActionButton: Consumer<HYCounterViewModel>(
    builder: (ctx, counterVM, child){
    print("floatingActionButton build方法被执行")//不执行
    return FloatingActionButton(
      child: child,
      onPressed: () {
        setState(() {
          counterVM.counter += 1;
        });
      },
    ),
    child: Icon(Icons.add),
  }
)
  
⚠️优化方式二: ✅: FloatingActionButton也不需要重新构建, 由Consumer替换为Selector
  Selector 的作用: 
        1.可以对原有的HYCounterViewModel数据进行转化; 
        2.shouldRebuild 要不要重新构建, 返回false就不再构建了

floatingActionButton: Consumer<HYCounterViewModel, HYCounterViewModel>(
  selector: (ctx, counterVM) => counterVM,
  shouldRebuild: (prev, next) => false,
    builder: (ctx, counterVM, child){
    print("floatingActionButton build方法被执行")//不执行
    return FloatingActionButton(
      child: child,
      onPressed: () {
        setState(() {
          counterVM.counter += 1;
        });
      },
    ),
    child: Icon(Icons.add),
  }
)

2.2. Provider的进阶用法

  1. 在main.dart中: 使用多份共享数据Provider
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (ctx) => HYCounterViewModel(),),
        ChangeNotifierProvider(create: (ctx) => HYUserViewModel(UserInfo("why",29,"abc")),),
      ],
      child: const MyApp(),
    )
  );
}

⚠️优化: 可以将数组抽取成一个类, 方便统一维护:

新建initlalize_providers.dart中:

import 'package:flutter_dbctest/counter_view_model.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

List<SingleChildWidget> providers = [
  ChangeNotifierProvider(create: (ctx) => HYCounterViewModel()),
  ChangeNotifierProvider(create: (ctx) => HYUserViewModel(UserInfo("why",29,"abc")),),
];


在main.dart中:
import 'initlalize_providers.dart';

void main() {
  runApp(
    MultiProvider(
      providers: providers,
      child: const MyApp(),
    )
  );
}
  1. 同时使用多个provider的数据--Consumer2

    class HYShowData03 extentds StatelessWidget{
      @override
      Widget build(BuildContext context){
        return Card(
         color: Colors.red,
          child: Consumer2<HYUserViewModel, HYCounterViewModel>(//同时使用多个Provider
             build: (ctx, userVM, counterVM, child){
              return Text("当前计数: ${counterVM.counter} nickName:${userVM.nickname}", style: TextStyle(fontSize: 30));
            }
          )
        );
      }
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容