序言
做开发有很长一段时间了,现在处于离职Gap阶段,这期间一是休息调整状态(主要天挺热的),二是对工作期间的总结。我入行是iOS开发,后面为了提高效率,以及对齐不同端的业务逻辑,团队选择了Flutter,以module方式做混合开发。最初使用的Flutter版本是2.5.3,2年后我们把Flutter版本升级到了3.7.12,写文章时最新的版本是3.32.8,对之前所学的内容做一个总结和更新。
基本类型
- Dart里面的基本类型包括 Number(int,double)、List、String、bool、Function、Set、Map等;这些基本类型不管什么语言都有,区别可能就是Dart中都是对象类型,在使用对比起来,确实Dart体验要好一点(Api统一、一致的错误处理,也符合一切皆对象的思想),也更安全。
- 新增了Record类型,声明时也支持位置参数和命名参数 (类型A,类型B)或({类型A,类型B})。Record类型用处还是很大的,在3.7.12版本是没有这个类型的,比如数值类的展示,有些公司会要求超过万亿要转化为数值+单位的形式展示,再比如有些数据处理完后要返回多个结果,当然可以采用创建类或使用Map去解决,但是有时候真没必要(冗余一个类、Map的key还要定义常量)。这时候Record类型就太方便了。
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
- 增强型枚举
enum PlanetType { terrestrial, gas, ice }
enum Planet {
mercury(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
venus(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
uranus(planetType: PlanetType.ice, moons: 27, hasRings: true),
neptune(planetType: PlanetType.ice, moons: 14, hasRings: true);
/// A constant generating constructor
const Planet({
required this.planetType,
required this.moons,
required this.hasRings,
});
/// All instance variables are final
final PlanetType planetType;
final int moons;
final bool hasRings;
bool get isGiant => planetType == PlanetType.gas || planetType == PlanetType.ice;
}
- 类修饰符:
abstract:定义不能被实例化的类,通常包含抽象方法
base:只能被继承(extends),不能被实现(implements),确保子类继承父类的实现
final:不能被继承或实现,创建不可扩展的类
interface:只能被实现(implements),不能被继承(extends),定义契约
sealed:密封类,创建有限的子类集合,常用于模式匹配,只能在同一库中被继承
mixin:提供可重用的代码片段,可以被多个类混入
异步
- Future:
/// 方式一
Future(() => {},).then((value) {}).catchError((error) {}).whenComplete(() {});
/// 方式二:使用async/await
- Stream:实际工作中基本没遇到过,也没使用过,TODO一下,后面着重学习下;
IsoLate
Flutter是单线程,在主ioslate中运行。每一个Dart的isolate都是独立的运行线程,他们无法与其他isolate共享内存,因此也不会存在锁的问题。不同的isolate之间只能通过互发消息通信,所以开销是低于多线程的。
1.spawnUri():
spawnUri方法有三个必须的参数,
第一个是Uri,指定一个新Isolate代码文件的路径,
第二个是参数列表,类型是List,
第三个是动态消息。
需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,
该main函数中的args参数列表,正对应spawnUri中的第二个参数。
如不需要向新Isolate中传参数,该参数可传空List
import 'dart:isolate';
void main() {
print("main isolate start");
create_isolate();
print("main isolate stop");
}
// 创建一个新的 isolate
create_isolate() async {
ReceivePort rp = new ReceivePort();
SendPort port1 = rp.sendPort;
Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1);
SendPort port2;
rp.listen((message) {
print("main isolate message: $message");
if (message[0] == 0){
port2 = message[1];
}else{
port2?.send([1,"这条信息是 main isolate 发送的"]);
}
});
// 可以在适当的时候,调用以下方法杀死创建的 isolate
// newIsolate.kill(priority: Isolate.immediate);
}
创建【other_task.dart】,编写新的Isolate代码
import 'dart:isolate';
import 'dart:io';
void main(args, SendPort port1) {
print("isolate_1 start");
print("isolate_1 args: $args");
ReceivePort receivePort = new ReceivePort();
SendPort port2 = receivePort.sendPort;
receivePort.listen((message){
print("isolate_1 message: $message");
});
// 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信
port1.send([0, port2]); // 模拟耗时5秒
sleep(Duration(seconds:5));
port1.send([1, "isolate_1 任务完成"]);
print("isolate_1 stop");
}
2.spawn();
除了使用spawnUri,更常用的是使用spawn方法来创建新的Isolate,
我们通常希望将新创建的Isolate代码和main Isolate代码写在同一个文件,
且不希望出现两个main函数,而是将指定的耗时函数运行在新的Isolate,
这样做有利于代码的组织和代码的复用。
spawn方法有两个必须的参数,
第一个是需要运行在新Isolate的耗时函数,
第二个是动态消息,该参数通常用于传送主Isolate的SendPort对象。
import 'dart:isolate';
import 'dart:io';
void main() {
print("main isolate start");
create_isolate();
print("main isolate end");
}
// 创建一个新的 isolate
create_isolate() async{
ReceivePort rp = new ReceivePort();
SendPort port1 = rp.sendPort;
Isolate newIsolate = await Isolate.spawn(doWork, port1);
SendPort port2;
rp.listen((message){
print("main isolate message: $message");
if (message[0] == 0){
port2 = message[1];
}else{
port2?.send([1,"这条信息是 main isolate 发送的"]);
}
});
}
// 处理耗时任务
void doWork(SendPort port1){
print("new isolate start");
ReceivePort rp2 = new ReceivePort();
SendPort port2 = rp2.sendPort;
rp2.listen((message){
print("doWork message: $message");
});
// 将新isolate中创建的SendPort发送到主isolate中用于通信
port1.send([0, port2]);
// 模拟耗时5秒
sleep(Duration(seconds:5));
port1.send([1, "doWork 任务完成"]);
print("new isolate end");
}
无论如何,在Dart中创建一个Isolate都显得有些繁琐,可惜的是Dart官方并未提供更高级封装。但是,如果想在Flutter中创建Isolate,则有更简便的API,这是由Flutter官方进一步封装ReceivePort而提供的更简洁API。
使用compute函数来创建新的Isolate并执行耗时任务
import 'package:flutter/foundation.dart';
import 'dart:io';
// 创建一个新的Isolate,在其中运行任务doWork
create_new_task() async{
var str = "New Task";
var result = await compute(doWork, str);
print(result);
}
String doWork(String value){
print("new isolate doWork start");
// 模拟耗时5秒
sleep(Duration(seconds:5));
print("new isolate doWork end");
return "complete:$value";
}
compute函数有两个必须的参数,第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,第二个参数为动态的消息类型,可以是被运行函数的参数。需要注意,使用compute应导入'package:flutter/foundation.dart'包。