美文网首页
Flutter & Dart 基础

Flutter & Dart 基础

作者: 微微笑的蜗牛 | 来源:发表于2019-12-04 21:32 被阅读0次

    以下主要是学习极客时间 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 = <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 关键字,通过 _ 来区分是 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 基础

          本文链接:https://www.haomeiwen.com/subject/jgcrgctx.html