第九部分 异步操作与网络封装
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的进阶用法
- 在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(),
)
);
}
-
同时使用多个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)); } ) ); } }