以下主要是學習極客時間 Flutter
專欄相關學習記錄。
Dart 基礎
核心特性
JIT & AOT
Dart
同時支持 JIT
和 AOT
兩種編譯方式。
-
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)無鎖快速分配。
垃圾回收
垃圾回收,采用多生代算法。
-
調(diào)度器
檢測到程序處于空閑狀態(tài),沒有用戶交互時,進行回收,減少
GC
對性能的影響。 -
年輕代
新生代空間收集器,清除壽命較短的短暫對象。
由于對象被分配在連續(xù)空間中,在創(chuàng)建對象時,他們被分配到可用空間,直到分配的內(nèi)存填充完畢。
分配給新對象的連續(xù)空間由兩部分組成,任何時候只使用一半,分為活動空間和非活動空間。新生成的對象在活動區(qū)間,一旦填充完畢,
不可回收對象
從活動空間復制到非活動空間。活動空間進行清理,非活動空間轉(zhuǎn)變?yōu)榛顒涌臻g。過程如下圖所示:
-
老年代
并行標記掃描收集器,清除生命周期比較長的對象。采用標記整理的方法回收對象。
a. 遍歷對象圖,標記仍在使用的對象。
b. 掃描,回收未被標記的對象,清除標記。
如下圖所示:
單線程模型
Dart
是單線程模型,通過 Event Loop
實現(xiàn)異步。
- 微任務隊列:短時間會完成的任務,比事件隊列優(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
中完成,使用 await
。await
、async
用法 與 js
中的類似。
并發(fā)
Dart
中沒有線程,并發(fā)通過 Isolate
實現(xiàn),并且 Isolate
之間不共享內(nèi)存。每個Isolate
有自己獨立的堆棧,Event Loop
和 Queue
。它們之間通過消息機制(發(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
與 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',};
推薦顯式聲明,可讀性好,當添加不匹配類型時,編譯器根據(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ū)分是 private
。private
的限制是庫訪問級別。
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
,若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
層面表現(xiàn)高度一致。底層基于 Skia
,C++
編寫的強大的跨平臺圖形繪制引擎。
與RN
不同的是,Fluter
是自己完成了組件的渲染。
而 RN
是通過 JS 虛擬機
作為橋梁調(diào)用到原生組件,最終生成的還是各端的原生組件,兩端會有差異性。
Widget
widget
:一種抽象的結構化信息描述。
Flutter
中一切皆為 widget
,比如應用、視圖、布局等。
Widget 渲染過程
涉及到三部分:Widget
、Element
、RenderObject
,其中 Element
連接 Widget
和 RenderObject
,分別持有。
Widget:不可變,配置信息發(fā)生變化時,會重建
Widget
樹,但它并不涉及到視圖的渲染,重建成本低。Element:可變,可以看成
Widget
對應的一種數(shù)據(jù)結構,一個實例化的對象,是視圖配置信息到最終渲染的橋梁。將Widget
的變化做了一層封裝,類似于React
中的虛擬DOM diff
,只將需要改動的部分同步給RenderObject
,提高渲染效率。RenderObject:真正的渲染對象,負責布局與繪制。之后的合成和渲染,交給
Skia
。
深度優(yōu)先遍歷 Widget
樹 --> 生成對應節(jié)點的 Element
對象 --> 生成 RenderObject
。
Widget
變化時,持有該 Widget
的 Element
會設置為 dirty
,觸發(fā) Element
和 RenderObject
樹的更新。
與原生混編
將 Flutter
工程成為原生工程的子模塊,抽離 Fluter
工程,按照不同平臺的構建產(chǎn)物按照組件化的方式進行管理。
其中原生工程對 Fluter
的依賴主要是:
- Flutter 的 Framework 和引擎庫。
- 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)絡請求,是請求 - 響應式。
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)用。