Flutter & Dart 基礎

以下主要是學習極客時間 Flutter 專欄相關學習記錄。

Dart 基礎

Online Dart iDE

核心特性

JIT & AOT

Dart 同時支持 JITAOT 兩種編譯方式。

  • JIT:Just In Time,運行時即時編譯,開發(fā)效率高。可動態(tài)下發(fā)和執(zhí)行代碼,運行速度和性能受到影響。Flutter 的熱重載基礎這一特性。( 開發(fā) Debug 模式使用)

    image.png
將 `Dart` 編譯生成中間代碼 `Script Snapshot`,由   `Dart VM` 解釋執(zhí)行。 
  • AOT:Ahead Of Time,需要預先編譯,開發(fā)效率低。但運行速度快,性能表現(xiàn)好。(發(fā)布 Release 模式使用)

    image.png

    編譯生成設備對應的二進制。

內(nèi)存分配與垃圾回收

內(nèi)存分配

Dart VM 的內(nèi)存分配,只需要在堆上移動指針,內(nèi)存增長是線性的。

Dart 通過 Isolate 實現(xiàn)并發(fā),但并不共享內(nèi)存,可以實現(xiàn)無鎖快速分配。

垃圾回收

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

  1. 調(diào)度器

    檢測到程序處于空閑狀態(tài),沒有用戶交互時,進行回收,減少 GC 對性能的影響。

  2. 年輕代

    新生代空間收集器,清除壽命較短的短暫對象。

    由于對象被分配在連續(xù)空間中,在創(chuàng)建對象時,他們被分配到可用空間,直到分配的內(nèi)存填充完畢。

    分配給新對象的連續(xù)空間由兩部分組成,任何時候只使用一半,分為活動空間和非活動空間。新生成的對象在活動區(qū)間,一旦填充完畢,不可回收對象從活動空間復制到非活動空間。活動空間進行清理,非活動空間轉(zhuǎn)變?yōu)榛顒涌臻g。

    過程如下圖所示:

image.png
  1. 老年代

    并行標記掃描收集器,清除生命周期比較長的對象。采用標記整理的方法回收對象。

    a. 遍歷對象圖,標記仍在使用的對象。

    b. 掃描,回收未被標記的對象,清除標記。

    如下圖所示:

image.png

單線程模型

Dart 是單線程模型,通過 Event Loop 實現(xiàn)異步。

image.png
  • 微任務隊列:短時間會完成的任務,比事件隊列優(yōu)先級更高,當其不為空,會一直占著事件循環(huán)。一般情況不會用到。目前只有 Flutter 內(nèi)部用到,如手勢識別,滾動視圖等高優(yōu)先級操作。
  • 事件隊列:用得較多,如定時器、IO 等。
Future

使用 Future 將同步任務包裝成異步任務,把函數(shù)體放到事件隊列中,立即返回。后面代碼同步執(zhí)行,執(zhí)行完成后,從事件隊列中取出事件,依次同步執(zhí)行函數(shù)體及后續(xù)的 then

Future(() => print('Running in Future 1'));//下一個事件循環(huán)輸出字符串

Future(() => print('Running in Future 2'))
  .then((_) => print('and then 1'))
  .then((_) => print('and then 2'));//上一個事件循環(huán)結束后,連續(xù)輸出三段字符串
  
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 中的類似。

并發(fā)

Dart 中沒有線程,并發(fā)通過 Isolate 實現(xiàn),并且 Isolate 之間不共享內(nèi)存。每個Isolate 有自己獨立的堆棧,Event LoopQueue。它們之間通過消息機制(發(fā)送管道 sendPort)進行通信。

比如,主 Isolate 向并發(fā)Isolate傳入自己的發(fā)送管道,并監(jiān)聽管道消息。這樣,并發(fā) Isolate 就可以通過該管道發(fā)送消息。


Isolate isolate;

start() async {
  ReceivePort receivePort= ReceivePort();//創(chuàng)建管道
  
  //創(chuàng)建并發(fā)Isolate,并傳入發(fā)送管道
  isolate = await Isolate.spawn(getMsg, receivePort.sendPort);
  
  //監(jiān)聽管道消息
  receivePort.listen((data) {
    print('Data:$data');
    receivePort.close();//關閉管道
    isolate?.kill(priority: Isolate.immediate);//殺死并發(fā)Isolate
    isolate = null;
  });
}

//并發(fā)Isolate往管道發(fā)送一個字符串
getMsg(sendPort) => sendPort.send("Hello");

基礎語法

變量與類型

  • 未初始化的變量值都為 null

  • 可自行指定類型,或由編譯器推導。

    var:表示類型由編譯器判斷。

  • 所有類型都是對象類型,繼承自 Object

  • 基本數(shù)據(jù)類型: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',};

推薦顯式聲明,可讀性好,當添加不匹配類型時,編譯器根據(jù)聲明類型做錯誤提示。

常量定義

const:在編譯期確定的值,適用于定義字面量。

const count = 3;

final:在運行期確定,一旦確定,不能修改。

var x = 70; 
var y = 30;
final z = x / y;

函數(shù)

函數(shù)定義

bool isZero(int number) { 
    //判斷整數(shù)是否為0 
    return number == 0; 
}

若函數(shù)只有一行,可以使用箭頭函數(shù)來簡化函數(shù)定義。

bool isZero(int number) => number == 0;

參數(shù)

可選命名參數(shù)

給參數(shù)添加 {},類似于 map,調(diào)用時指定傳入哪些參數(shù),位置隨意。

定義:

// 可選命名參數(shù)
void enable1Flags({bool bold, bool hidden}) => print("$bold , $hidden");

調(diào)用:

enable1Flags(bold: true, hidden: false); //true, false
enable1Flags(bold: true); //true, null
可選參數(shù)

給參數(shù)加上 [],即這些參數(shù)是可選的,可設置默認值。

定義:

void enable4Flags(bool bold, [bool hidden = false]) => print("$bold ,$hidden");

調(diào)用:

enable4Flags(true); //true, false

定義與 Java 類似,但沒有 public、protected、private 關鍵字,通過 _ 來區(qū)分是 privateprivate 的限制是庫訪問級別。

class Point { 
    num x, y; 
    static num factor = 0; 
    
    //語法糖,等同于在函數(shù)體內(nèi):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

為了使得實例化語義清晰化,支持命名構造函數(shù),類似指定構造函數(shù)。

// 命名構造函數(shù)
Point.bottom(num x) : this(x, 0);

// 實例化
var p = Point.bottom(100);

復用

繼承與接口實現(xiàn)。

  • 繼承會自動獲取父類的成員變量和方法實現(xiàn),子類可以根據(jù)需要覆寫構造函數(shù)及父類方法;
  • 接口實現(xiàn),需要重新實現(xiàn)成員變量,以及方法的聲明和初始化,否則編譯器會報錯。
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的接口實現(xiàn)
class Coordinate implements Point { 
    // 成員變量需要重新聲明 
    num x = 0, y = 0; 

    // 成員函數(shù)需要重新聲明實現(xiàn)
    void printInfo() => print('($x,$y)'); 
}
Mixin

Mixin,即混入,為了解決多繼承帶來的問題。使用混入復用代碼,并不是繼承關系 is a,類似組合 has a。通過這種方式可以調(diào)用混入類的變量和方法,使用 with 關鍵字即可。

注意,如果混入的多個類中有同名的方法且被調(diào)用,會以最后一個混入類為準。

class Coordinate with Point {
}

var x = Coordinate();

// 調(diào)用 Point 類中的方法
x.printInfo();

運算符

  • ?.p?.printInfo(),若 p 為空,則不調(diào)用。類似于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 層面表現(xiàn)高度一致。底層基于 SkiaC++ 編寫的強大的跨平臺圖形繪制引擎。

RN不同的是,Fluter 是自己完成了組件的渲染。
RN 是通過 JS 虛擬機作為橋梁調(diào)用到原生組件,最終生成的還是各端的原生組件,兩端會有差異性。

Widget

widget:一種抽象的結構化信息描述。

Flutter 中一切皆為 widget,比如應用、視圖、布局等。

Widget 渲染過程

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

image.png
  • Widget:不可變,配置信息發(fā)生變化時,會重建 Widget 樹,但它并不涉及到視圖的渲染,重建成本低。

  • Element:可變,可以看成 Widget 對應的一種數(shù)據(jù)結構,一個實例化的對象,是視圖配置信息到最終渲染的橋梁。將 Widget 的變化做了一層封裝,類似于 React 中的虛擬 DOM diff,只將需要改動的部分同步給 RenderObject,提高渲染效率。

  • RenderObject:真正的渲染對象,負責布局與繪制。之后的合成和渲染,交給 Skia

深度優(yōu)先遍歷 Widget 樹 --> 生成對應節(jié)點的 Element 對象 --> 生成 RenderObject

Widget變化時,持有該 WidgetElement 會設置為 dirty,觸發(fā) ElementRenderObject樹的更新。

與原生混編

Flutter 工程成為原生工程的子模塊,抽離 Fluter 工程,按照不同平臺的構建產(chǎn)物按照組件化的方式進行管理。

其中原生工程對 Fluter 的依賴主要是:

  1. Flutter 的 Framework 和引擎庫。
  2. Flutter 工程,即我們自己實現(xiàn)的功能。

通過 flutter build 生成各自平臺的產(chǎn)物。

Android:打成 aar 引入,在 build.gradle 中添加依賴。
iOS:作為獨立 pod 引入,創(chuàng)建 podSpec,在 podFile 中添加依賴。

調(diào)用原生能力

因為 Fluter 只接管了渲染層,所以原生系統(tǒng)能力無法提供,如藍牙、拍照、定位等等。

通過方法通道MethodChannel 方式,與原生進行通信。類似網(wǎng)絡請求,是請求 - 響應式。

image.png

Flutter 調(diào)用:

//聲明MethodChannel
const platform = MethodChannel('samples.chenhang/utils');

//異步等待方法通道的調(diào)用結果 
result = await platform.invokeMethod('openAppMarket');

原生處理:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  //創(chuàng)建命名方法通道
  FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"samples.chenhang/utils" binaryMessenger:(FlutterViewController *)self.window.rootViewController];
  
  //往方法通道注冊方法調(diào)用處理回調(diào)
  [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 {
      //找不到被調(diào)用的方法
      result(FlutterMethodNotImplemented);
    }
  }];
  ...
}

同樣原生也可以調(diào)用 Flutter 方法,首先在 Flutter 方實現(xiàn)接口回調(diào)。
Native使用 [channel invokeMethod:@"xx" arguments:xx result:^(id _Nullable result) {}]; 進行調(diào)用。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容