以下主要是学习极客时间 Flutter
专栏相关学习记录。
Dart 基础
核心特性
JIT & AOT
Dart
同时支持 JIT
和 AOT
两种编译方式。
-
JIT:Just In Time,运行时即时编译,开发效率高。可动态下发和执行代码,运行速度和性能受到影响。
Flutter
的热重载基础这一特性。( 开发Debug
模式使用)
将 `Dart` 编译生成中间代码 `Script Snapshot`,由 `Dart VM` 解释执行。
-
AOT:Ahead Of Time,需要预先编译,开发效率低。但运行速度快,性能表现好。(发布
Release
模式使用)编译生成设备对应的二进制。
内存分配与垃圾回收
内存分配
Dart VM
的内存分配,只需要在堆上移动指针,内存增长是线性的。
Dart
通过 Isolate
实现并发,但并不共享内存,可以实现无锁快速分配。
垃圾回收
垃圾回收,采用多生代算法。
-
调度器
检测到程序处于空闲状态,没有用户交互时,进行回收,减少
GC
对性能的影响。 -
年轻代
新生代空间收集器,清除寿命较短的短暂对象。
由于对象被分配在连续空间中,在创建对象时,他们被分配到可用空间,直到分配的内存填充完毕。
分配给新对象的连续空间由两部分组成,任何时候只使用一半,分为活动空间和非活动空间。新生成的对象在活动区间,一旦填充完毕,
不可回收对象
从活动空间复制到非活动空间。活动空间进行清理,非活动空间转变为活动空间。过程如下图所示:
-
老年代
并行标记扫描收集器,清除生命周期比较长的对象。采用标记整理的方法回收对象。
a. 遍历对象图,标记仍在使用的对象。
b. 扫描,回收未被标记的对象,清除标记。
如下图所示:
单线程模型
Dart
是单线程模型,通过 Event Loop
实现异步。
- 微任务队列:短时间会完成的任务,比事件队列优先级更高,当其不为空,会一直占着事件循环。一般情况不会用到。目前只有
Flutter
内部用到,如手势识别,滚动视图等高优先级操作。 - 事件队列:用得较多,如定时器、IO 等。
Future
使用 Future
将同步任务包装成异步任务,把函数体放到事件队列中,立即返回。后面代码同步执行,执行完成后,从事件队列中取出事件,依次同步执行函数体及后续的 then
。
Future(() => print('Running in Future 1'));//下一个事件循环输出字符串
Future(() => print('Running in Future 2'))
.then((_) => print('and then 1'))
.then((_) => print('and then 2'));//上一个事件循环结束后,连续输出三段字符串
Future(() => print('Running in Future 3'));
print('hello');
结果:
hello
Running in Future 1
Running in Future 2
and then 1
and then 2
Running in Future 3
若要同步等待 Future
中完成,使用 await
。await
、async
用法 与 js
中的类似。
并发
Dart
中没有线程,并发通过 Isolate
实现,并且 Isolate
之间不共享内存。每个Isolate
有自己独立的堆栈,Event Loop
和 Queue
。它们之间通过消息机制(发送管道 sendPort
)进行通信。
比如,主 Isolate
向并发Isolate
传入自己的发送管道,并监听管道消息。这样,并发 Isolate
就可以通过该管道发送消息。
Isolate isolate;
start() async {
ReceivePort receivePort= ReceivePort();//创建管道
//创建并发Isolate,并传入发送管道
isolate = await Isolate.spawn(getMsg, receivePort.sendPort);
//监听管道消息
receivePort.listen((data) {
print('Data:$data');
receivePort.close();//关闭管道
isolate?.kill(priority: Isolate.immediate);//杀死并发Isolate
isolate = null;
});
}
//并发Isolate往管道发送一个字符串
getMsg(sendPort) => sendPort.send("Hello");
基础语法
变量与类型
未初始化的变量值都为
null
。-
可自行指定类型,或由编译器推导。
var
:表示类型由编译器判断。 所有类型都是对象类型,继承自
Object
。基本数据类型:num、bool、String、List、Map。
num:64 位整形
Int
,64 位浮点型Double
。bool:true、false。需要显式进行比较。
if (a != 0) {}
String:单双引号都可以,多行用三个单引号或双引号。
List、Map
与 Java
、Swift
类似。
List:
// 自动推导元素类型为 `String`
var arr1 = ["Tom", "Andy", "Jack"];
// 显式声明
var arr1 = <String>['Tom', 'Andy', 'Jack'];
Map:
// 自动推导类型为 `Map<String: String>`
var map1 = {"name": "Tom", 'sex': 'male'};
// 显式声明
var map1 = <String, String>{'name': 'Tom','sex': 'male',};
推荐显式声明,可读性好,当添加不匹配类型时,编译器根据声明类型做错误提示。
常量定义
const:在编译期确定的值,适用于定义字面量。
const count = 3;
final:在运行期确定,一旦确定,不能修改。
var x = 70;
var y = 30;
final z = x / y;
函数
函数定义
bool isZero(int number) {
//判断整数是否为0
return number == 0;
}
若函数只有一行,可以使用箭头函数来简化函数定义。
bool isZero(int number) => number == 0;
参数
可选命名参数
给参数添加 {}
,类似于 map
,调用时指定传入哪些参数,位置随意。
定义:
// 可选命名参数
void enable1Flags({bool bold, bool hidden}) => print("$bold , $hidden");
调用:
enable1Flags(bold: true, hidden: false); //true, false
enable1Flags(bold: true); //true, null
可选参数
给参数加上 []
,即这些参数是可选的,可设置默认值。
定义:
void enable4Flags(bool bold, [bool hidden = false]) => print("$bold ,$hidden");
调用:
enable4Flags(true); //true, false
类
定义与 Java
类似,但没有 public、protected、private
关键字,通过 _
来区分是 private
。private
的限制是库访问级别。
class Point {
num x, y;
static num factor = 0;
//语法糖,等同于在函数体内:this.x = x;this.y = y;
Point(this.x,this.y);
void printInfo() => print('($x, $y)');
static void printZValue() => print('$factor');
}
var p = new Point(100,200); // new 关键字可以省略
p.printInfo(); // 输出(100, 200);
Point.factor = 10;
Point.printZValue(); // 输出10
为了使得实例化语义清晰化,支持命名构造函数,类似指定构造函数。
// 命名构造函数
Point.bottom(num x) : this(x, 0);
// 实例化
var p = Point.bottom(100);
复用
继承与接口实现。
- 继承会自动获取父类的成员变量和方法实现,子类可以根据需要覆写构造函数及父类方法;
- 接口实现,需要重新实现成员变量,以及方法的声明和初始化,否则编译器会报错。
class Point {
num x = 0, y = 0;
void printInfo() => print('($x,$y)');
}
Vector extends Point{
num z = 0;
@override
void printInfo() => print('($x,$y,$z)');
}
// Coordinate是对Point的接口实现
class Coordinate implements Point {
// 成员变量需要重新声明
num x = 0, y = 0;
// 成员函数需要重新声明实现
void printInfo() => print('($x,$y)');
}
Mixin
Mixin,即混入,为了解决多继承带来的问题。使用混入
复用代码,并不是继承关系 is a
,类似组合 has a
。通过这种方式可以调用混入类的变量和方法,使用 with
关键字即可。
注意,如果混入的多个类中有同名的方法且被调用,会以最后一个混入类为准。
class Coordinate with Point {
}
var x = Coordinate();
// 调用 Point 类中的方法
x.printInfo();
运算符
-
?.
,p?.printInfo()
,若p
为空,则不调用。类似于Swift
中的可选类型。 -
??=
,a ??= value
,若a
为null
,则给a
赋值value
。 -
??
,a ?? b
,若a
为空,则取b
;否则取a
。
自定义与复写
class Vector {
num x, y;
Vector(this.x, this.y);
// 自定义 +
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
// 覆写 ==
bool operator == (dynamic v) => x == v.x && y == v.y;
}
final x = Vector(3, 3);
final y = Vector(2, 2);
print(x == y);
print((x + y).x);
Flutter
跨平台 UI
渲染框架,重写了底层渲染逻辑。在 iOS
和 Android
上, UI
层面表现高度一致。底层基于 Skia
,C++
编写的强大的跨平台图形绘制引擎。
与RN
不同的是,Fluter
是自己完成了组件的渲染。
而 RN
是通过 JS 虚拟机
作为桥梁调用到原生组件,最终生成的还是各端的原生组件,两端会有差异性。
Widget
widget
:一种抽象的结构化信息描述。
Flutter
中一切皆为 widget
,比如应用、视图、布局等。
Widget 渲染过程
涉及到三部分:Widget
、Element
、RenderObject
,其中 Element
连接 Widget
和 RenderObject
,分别持有。
Widget:不可变,配置信息发生变化时,会重建
Widget
树,但它并不涉及到视图的渲染,重建成本低。Element:可变,可以看成
Widget
对应的一种数据结构,一个实例化的对象,是视图配置信息到最终渲染的桥梁。将Widget
的变化做了一层封装,类似于React
中的虚拟DOM diff
,只将需要改动的部分同步给RenderObject
,提高渲染效率。RenderObject:真正的渲染对象,负责布局与绘制。之后的合成和渲染,交给
Skia
。
深度优先遍历 Widget
树 --> 生成对应节点的 Element
对象 --> 生成 RenderObject
。
Widget
变化时,持有该 Widget
的 Element
会设置为 dirty
,触发 Element
和 RenderObject
树的更新。
与原生混编
将 Flutter
工程成为原生工程的子模块,抽离 Fluter
工程,按照不同平台的构建产物按照组件化的方式进行管理。
其中原生工程对 Fluter
的依赖主要是:
- Flutter 的 Framework 和引擎库。
- Flutter 工程,即我们自己实现的功能。
通过 flutter build
生成各自平台的产物。
Android:打成 aar
引入,在 build.gradle
中添加依赖。
iOS:作为独立 pod
引入,创建 podSpec
,在 podFile
中添加依赖。
调用原生能力
因为 Fluter
只接管了渲染层,所以原生系统能力无法提供,如蓝牙、拍照、定位等等。
通过方法通道MethodChannel
方式,与原生进行通信。类似网络请求,是请求 - 响应式。
Flutter 调用:
//声明MethodChannel
const platform = MethodChannel('samples.chenhang/utils');
//异步等待方法通道的调用结果
result = await platform.invokeMethod('openAppMarket');
原生处理:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//创建命名方法通道
FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"samples.chenhang/utils" binaryMessenger:(FlutterViewController *)self.window.rootViewController];
//往方法通道注册方法调用处理回调
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
//方法名称一致
if ([@"openAppMarket" isEqualToString:call.method]) {
//打开App Store
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"itms-apps://itunes.apple.com/xy/app/foo/id414478124"]];
//返回方法处理结果
result(@0);
} else {
//找不到被调用的方法
result(FlutterMethodNotImplemented);
}
}];
...
}
同样原生也可以调用 Flutter
方法,首先在 Flutter
方实现接口回调。
Native
使用 [channel invokeMethod:@"xx" arguments:xx result:^(id _Nullable result) {}];
进行调用。