Flutter & Dart 基础

以下主要是学习极客时间 Flutter 专栏相关学习记录。

Dart 基础

Online Dart iDE

核心特性

JIT & AOT

Dart 同时支持 JITAOT 两种编译方式。

  • JIT:Just In Time,运行时即时编译,开发效率高。可动态下发和执行代码,运行速度和性能受到影响。Flutter 的热重载基础这一特性。( 开发 Debug 模式使用)

    image.png
将 `Dart` 编译生成中间代码 `Script Snapshot`,由   `Dart VM` 解释执行。 
  • AOT:Ahead Of Time,需要预先编译,开发效率低。但运行速度快,性能表现好。(发布 Release 模式使用)

    image.png

    编译生成设备对应的二进制。

内存分配与垃圾回收

内存分配

Dart VM 的内存分配,只需要在堆上移动指针,内存增长是线性的。

Dart 通过 Isolate 实现并发,但并不共享内存,可以实现无锁快速分配。

垃圾回收

垃圾回收,采用多生代算法。

  1. 调度器

    检测到程序处于空闲状态,没有用户交互时,进行回收,减少 GC 对性能的影响。

  2. 年轻代

    新生代空间收集器,清除寿命较短的短暂对象。

    由于对象被分配在连续空间中,在创建对象时,他们被分配到可用空间,直到分配的内存填充完毕。

    分配给新对象的连续空间由两部分组成,任何时候只使用一半,分为活动空间和非活动空间。新生成的对象在活动区间,一旦填充完毕,不可回收对象从活动空间复制到非活动空间。活动空间进行清理,非活动空间转变为活动空间。

    过程如下图所示:

image.png
  1. 老年代

    并行标记扫描收集器,清除生命周期比较长的对象。采用标记整理的方法回收对象。

    a. 遍历对象图,标记仍在使用的对象。

    b. 扫描,回收未被标记的对象,清除标记。

    如下图所示:

image.png

单线程模型

Dart 是单线程模型,通过 Event Loop 实现异步。

image.png
  • 微任务队列:短时间会完成的任务,比事件队列优先级更高,当其不为空,会一直占着事件循环。一般情况不会用到。目前只有 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 中完成,使用 awaitawaitasync 用法 与 js 中的类似。

并发

Dart 中没有线程,并发通过 Isolate 实现,并且 Isolate 之间不共享内存。每个Isolate 有自己独立的堆栈,Event LoopQueue。它们之间通过消息机制(发送管道 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

JavaSwift 类似。

List:

// 自动推导元素类型为 `String`
var arr1 = ["Tom", "Andy", "Jack"];

// 显式声明
var arr1 = ['Tom', 'Andy', 'Jack'];

Map:

// 自动推导类型为 `Map`
var map1 = {"name": "Tom", 'sex': 'male'};

// 显式声明
var map1 = {'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 关键字,通过 _ 来区分是 privateprivate 的限制是库访问级别。

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,若 anull,则给 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 渲染框架,重写了底层渲染逻辑。在 iOSAndroid 上, UI 层面表现高度一致。底层基于 SkiaC++ 编写的强大的跨平台图形绘制引擎。

RN不同的是,Fluter 是自己完成了组件的渲染。
RN 是通过 JS 虚拟机作为桥梁调用到原生组件,最终生成的还是各端的原生组件,两端会有差异性。

Widget

widget:一种抽象的结构化信息描述。

Flutter 中一切皆为 widget,比如应用、视图、布局等。

Widget 渲染过程

涉及到三部分:WidgetElementRenderObject,其中 Element 连接 WidgetRenderObject,分别持有。

image.png
  • Widget:不可变,配置信息发生变化时,会重建 Widget 树,但它并不涉及到视图的渲染,重建成本低。

  • Element:可变,可以看成 Widget 对应的一种数据结构,一个实例化的对象,是视图配置信息到最终渲染的桥梁。将 Widget 的变化做了一层封装,类似于 React 中的虚拟 DOM diff,只将需要改动的部分同步给 RenderObject,提高渲染效率。

  • RenderObject:真正的渲染对象,负责布局与绘制。之后的合成和渲染,交给 Skia

深度优先遍历 Widget 树 --> 生成对应节点的 Element 对象 --> 生成 RenderObject

Widget变化时,持有该 WidgetElement 会设置为 dirty,触发 ElementRenderObject树的更新。

与原生混编

Flutter 工程成为原生工程的子模块,抽离 Fluter 工程,按照不同平台的构建产物按照组件化的方式进行管理。

其中原生工程对 Fluter 的依赖主要是:

  1. Flutter 的 Framework 和引擎库。
  2. Flutter 工程,即我们自己实现的功能。

通过 flutter build 生成各自平台的产物。

Android:打成 aar 引入,在 build.gradle 中添加依赖。
iOS:作为独立 pod 引入,创建 podSpec,在 podFile 中添加依赖。

调用原生能力

因为 Fluter 只接管了渲染层,所以原生系统能力无法提供,如蓝牙、拍照、定位等等。

通过方法通道MethodChannel 方式,与原生进行通信。类似网络请求,是请求 - 响应式。

image.png

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) {}]; 进行调用。

你可能感兴趣的:(Flutter & Dart 基础)