谷歌大會之後,有不少人諮詢了我 Flutter 相關的問題,其中有不少是和麵試相關的,如今一些招聘上也開始羅列 Flutter 相關要求,最後想了想還是寫一期總結吧,也算是 Flutter 的階段複習。
⚠️系統完整的學習是必須需要的,這裡只能幫你總結一些知識點,更多的還請查閱 Dart/Flutter 官網。
本篇主要是知識點總結,如有疑問可點選各文章連結瞭解詳情,或者查閱我 掘金專欄。
Dart 部分
其實學習過 JavaScript
或者 Java/Kotlin
的人,在學習 Dart
上幾乎是沒什麼難度的,Dart 綜合了動態語言和靜態語言的特性, 這裡主要提供一些不一樣,或者有意思的概念。
-
1、
Dart
屬於是強型別語言 ,但可以用var
來宣告變數,Dart
會自推匯出資料型別,var
實際上是編譯期的“語法糖”。dynamic
表示動態型別, 被編譯後,實際是一個object
型別,在編譯期間不進行任何的型別檢查,而是在執行期進行型別檢查。 -
2、
Dart
中if
等語句只支援bool
型別,switch
支援 String 型別。 -
3、
Dart
中陣列和List
是一樣的。 -
4、
Dart
中,Runes
代表符號文字 , 是 UTF-32 編碼的字串, 用於如Runes input = new Runes('\u{1f596} \u{1f44d}');
-
5、
Dart
支援閉包。 -
6、
Dart
中 number 型別分為 int 和 double ,沒有 float 型別。 -
7、
Dart
中 級聯操作符 可以方便配置邏輯,如下程式碼:
event
..id = 1
..type = ""
..actor = "";
複製程式碼
- 8、賦值操作符
比較有意思的賦值操作符有:
AA ?? "999" ///表示如果 AA 為空,返回999
AA ??= "999" ///表示如果 AA 為空,給 AA 設定成 999
AA ~/999 ///AA 對於 999 整除
複製程式碼
- 9、可選方法引數
Dart
方法可以設定 引數預設值 和 指定名稱 。
比如: getDetail(Sting userName, reposName, {branch = "master"}){}
方法,這裡 branch 不設定的話,預設是 “master” 。引數型別 可以指定或者不指定。呼叫效果: getRepositoryDetailDao(“aaa", "bbbb", branch: "dev");
。
- 10、作用域
Dart
沒有關鍵詞 public
、private
等修飾符,_
下橫向直接代表 private
,但是有 @protected
註解 。
- 11、構造方法
Dart
中的多構造方法,可以通過命名方法實現。
預設構造方法只能有一個,而通過 Model.empty()
方法可以建立一個空引數的類,其實方法名稱隨你喜歡,而變數初始化值時,只需要通過 this.name
在構造方法中指定即可:
class ModelA {
String name;
String tag;
//預設構造方法,賦值給name和tag
ModelA(this.name, this.tag);
//返回一個空的ModelA
ModelA.empty();
//返回一個設定了name的ModelA
ModelA.forName(this.name);
}
複製程式碼
- 12、getter setter 重寫
Dart
中所有的基礎型別、類等都繼承 Object
,預設值是 NULL
, 自帶 getter
和 setter
,而如果是 final
或者 const
的話,那麼它只有一個 getter
方法,Object
都支援 getter、setter 重寫:
@override
Size get preferredSize {
return Size.fromHeight(kTabHeight + indicatorWeight);
}
複製程式碼
- 13、Assert(斷言)
assert
只在檢查模式有效,在開發過程中,assert(unicorn == null);
只有條件為真才正常,否則直接丟擲異常,一般用在開發過程中,某些地方不應該出現什麼狀態的判斷。
- 14、重寫運算子,如下所示過載
operator
後對類進行 +/- 操作。
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
複製程式碼
支援過載的操作符 :
- 類、介面、繼承
Dart
中沒有介面,類都可以作為介面,把某個類當做介面實現時,只需要使用 implements
,然後複寫父類方法即可。
Dart
中支援 mixins
,按照出現順序應該為extends
、 mixins
、implements
。
-
Zone
Dart
中可通過 Zone
表示指定程式碼執行的環境,類似一個沙盒概念,在 Flutter
中 C++ 執行 Dart
也是在 _runMainZoned
內執行 runZoned
方法啟動,而我們也可以通過 Zone
,在執行環境內捕獲全域性異常等資訊:
runZoned(() {
runApp(FlutterReduxApp());
}, onError: (Object obj, StackTrace stack) {
print(obj);
print(stack);
});
複製程式碼
同時你可以給 runZoned
註冊方法,在需要時執行回撥,如下程式碼所示,這樣的在一個 Zone
內任何地方,只要能獲取 onData
這個 ZoneUnaryCallback
,就都可以呼叫到 handleData
///最終需要處理的地方
handleData(result) {
print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
print(result);
}
///返回得到一個 ZoneUnaryCallback
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
///執行 ZoneUnaryCallback 返回資料
Zone.current.runUnary(onData, 2);
複製程式碼
非同步邏輯可以通過 scheduleMicrotask
可以插入非同步執行方法:
Zone.current.scheduleMicrotask((){
//todo something
});
複製程式碼
更多可參看 :《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》
-
Future
Future
簡單了說就是對 Zone
的封裝使用。
比如 Future.microtask
中主要是執行了 Zone
的 scheduleMicrotask
,而 result._complete
最後呼叫的是 _zone.runUnary
等等。
factory Future.microtask(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
複製程式碼
Dart
中可通過 async
/await
或者 Future
定義非同步操作,而事實上 async
/await
也只是語法糖,最終還是通過編譯器轉為 Future
。
有興趣看這裡 :
-
Stream
Stream
也是有對Zone
的另外一種封裝使用。
Dart 中另外一種非同步操作, async*
/ yield
或者 Stream
可定義 Stream
非同步, async*
/ yield
也只是語法糖,最終還是通過編譯器轉為 Stream
。
Stream 還支援同步操作。
1)、Stream
中主要有 Stream
、 StreamController
、StreamSink
和 StreamSubscription
四個關鍵物件,大致可以總結為:
-
StreamController
:如類名描述,用於整個Stream
過程的控制,提供各類介面用於建立各種事件流。 -
StreamSink
:一般作為事件的入口,提供如add
,addStream
等。 -
Stream
:事件源本身,一般可用於監聽事件或者對事件進行轉換,如listen
、where
。 -
StreamSubscription
:事件訂閱後的物件,表面上用於管理訂閱過等各類操作,如cacenl
、pause
,同時在內部也是事件的中轉關鍵。
2)、一般通過 StreamController
建立 Stream
;通過 StreamSink
新增事件;通過 Stream
監聽事件;通過 StreamSubscription
管理訂閱。
3)、Stream
中支援各種變化,比如map
、expand
、where
、take
等操作,同時支援轉換為 Future
。
更多可參看 :《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》
Flutter 部分
Flutter 和 React Native 不同主要在於 Flutter UI是直接通過 skia 渲染的 ,而 React Native 是將 js 中的控制元件轉化為原生控制元件,通過原生去渲染的 ,相關更多可檢視:《移動端跨平臺開發的深度解析》。
-
Flutter 中存在
Widget
、Element
、RenderObject
、Layer
四棵樹,其中Widget
與Element
是多對一的關係 , -
Element
中持有Widget
和RenderObject
, 而Element
與RenderObject
是一一對應的關係 , -
當
RenderObject
的isRepaintBoundary
為true
時,那麼個區域形成一個Layer
,所以不是每個RenderObject
都具有Layer
的,因為這受isRepaintBoundary
的影響。
更多相關可查閱 《Flutter完整開發實戰詳解(九、 深入繪製原理)》
-
Flutter 中
Widget
不可變,每次保持在一幀,如果發生改變是通過State
實現跨幀狀態儲存,而真實完成佈局和繪製陣列的是RenderObject
,Element
充當兩者的橋樑,State
就是儲存在Element
中。 -
Flutter 中的
BuildContext
只是介面,而Element
實現了它。 -
Flutter 中
setState
其實是呼叫了markNeedsBuild
,該方法內部標記此Element
為Dirty
,然後在下一幀WidgetsBinding.drawFrame
才會被繪製,這可以看出setState
並不是立即生效的。 -
Flutter 中
RenderObject
在attch
/layout
之後會通過markNeedsPaint();
使得頁面重繪,流程大概如下:
通過isRepaintBoundary 往上確定了更新區域,通過 requestVisualUpdate 方法觸發更新往下繪製。
- 正常情況
RenderObject
的佈局相關方法呼叫順序是 :layout
->performResize
->performLayout
->markNeedsPaint
, 但是使用者一般不會直接呼叫layout
,而是通過markNeedsLayout
,具體流程如下:
-
Flutter 中一般 json 資料從
String
轉為Object
的過程中都需要先經過Map
型別。 -
Flutter 中
InheritedWidget
一般用於狀態共享,如Theme
、Localizations
、MediaQuery
等,都是通過它實現共享狀態,這樣我們可以通過context
去獲取共享的狀態,比如ThemeData theme = Theme.of(context);
在
Element
的inheritFromWidgetOfExactType
方法實現裡,有一個Map<Type, InheritedElement> _inheritedWidgets
的物件。
_inheritedWidgets
一般情況下是空的,只有當父控制元件是InheritedWidget
或者本身是InheritedWidgets
時才會有被初始化,而當父控制元件是InheritedWidget
時,這個Map
會被一級一級往下傳遞與合併 。所以當我們通過
context
呼叫inheritFromWidgetOfExactType
時,就可以往上查詢到父控制元件的Widget
。
- Flutter 中預設主要通過
runtimeType
和key
判斷更新:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製程式碼
Flutter 中的生命週期
-
initState()
表示當前State
將和一個BuildContext
產生關聯,但是此時BuildContext
沒有完全裝載完成,如果你需要在該方法中獲取BuildContext
,可以new Future.delayed(const Duration(seconds: 0, (){//context});
一下。 -
didChangeDependencies()
在initState()
之後呼叫,當State
物件的依賴關係發生變化時,該方法被呼叫,初始化時也會呼叫。 -
deactivate()
當State
被暫時從檢視樹中移除時,會呼叫這個方法,同時頁面切換時,也會呼叫。 -
dispose()
Widget 銷燬了,在呼叫這個方法之前,總會先呼叫 deactivate()。 -
didUpdateWidge
當widget
狀態發生變化時,會呼叫。
-
通過
StreamBuilder
和FutureBuilder
我們可以快速使用Stream
和Future
快速構建我們的非同步控制元件: 《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》 -
Flutter 中
runApp
啟動入口其實是一個WidgetsFlutterBinding
,它主要是通過BindingBase
的子類GestureBinding
、ServicesBinding
、SchedulerBinding
、PaintingBinding
、SemanticsBinding
、RendererBinding
、WidgetsBinding
等,通過mixins
的組合而成的。 -
Flutter 中的 Dart 的執行緒是以事件迴圈和訊息佇列的形式存在,包含兩個任務佇列,一個是 microtask 內部佇列,一個是 event 外部佇列,而 microtask 的優先順序又高於 event 。
因為 microtask 的優先順序又高於 event, 同時會阻塞event 佇列,所以如果 microtask 太多就可能會對觸控、繪製等外部事件造成阻塞卡頓哦。
- Flutter 中存在四大執行緒,分別為
UI Runner
、GPU Runner
、IO Runner
,Platform Runner
(原生主執行緒) ,同時在 Flutter 中可以通過isolate
或者compute
執行真正的跨執行緒非同步操作。
PlatformView
Flutter 中通過 PlatformView
可以巢狀原生 View
到 Flutter
UI 中,這裡面其實是使用了 Presentation
+ VirtualDisplay
+ Surface
等實現的,大致原理就是:
使用了類似副屏顯示的技術,VirtualDisplay
類代表一個虛擬顯示器,呼叫 DisplayManager
的 createVirtualDisplay()
方法,將虛擬顯示器的內容渲染在一個 Surface
控制元件上,然後將 Surface
的 id 通知給 Dart,讓 engine 繪製時,在記憶體中找到對應的 Surface
畫面記憶體資料,然後繪製出來。em... 實時控制元件截圖渲染顯示技術。
-
Flutter 的 Debug 下是 JIT 模式,release下是AOT模式。
-
Flutter 中可以通過
mixins AutomaticKeepAliveClientMixin
,然後重寫wantKeepAlive
保持住頁面,記得在被保持住的頁面build
中呼叫super.build
。(因為 mixins 特性)。 -
Flutter 手勢事件主要是通過競技判斷的:
主要有 hitTest
把所有需要處理的控制元件對應的 RenderObject
, 從 child
到 parent
全部組合成列表,從最裡面一直新增到最外層。
然後從佇列頭的 child 開始 for 迴圈執行 handleEvent
方法,執行 handleEvent
的過程不會被攔截打斷。
一般情況下 Down 事件不會決出勝利者,大部分時候是在 MOVE 或者 UP 的時候才會決出勝利者。
競技場關閉時只有一個的就直接勝出響應,沒有勝利者就拿排在佇列第一個強制勝利響應。
同時還有 didExceedDeadline
處理按住時的 Down 事件額外處理,同時手勢處理一般在 GestureRecognizer
的子類進行。
-
Flutter 中
ListView
滑動其實都是通過改變ViewPort
中的child
佈局來實現顯示的。 -
常用狀態管理的:目前有
scope_model
、flutter_redux
、fish_redux
、bloc + Stream
等幾種模式,具體可見 : 《Flutter完整開發實戰詳解(十二、全面深入理解狀態管理設計)》
Platform Channel
Flutter 中可以通過 Platform Channel
讓 Dart 程式碼和原生程式碼通訊的:
BasicMessageChannel
:用於傳遞字串和半結構化的資訊。MethodChannel
:用於傳遞方法呼叫(method invocation)。EventChanne
l: 用於資料流(event streams)的通訊。
同時 Platform Channel
並非是執行緒安全的 ,更多詳細可查閱閒魚技術的 《深入理解Flutter Platform Channel》
其中基礎資料型別對映如下:
Android 啟動頁
Android 中 Flutter
預設啟動時會在 FlutterActivityDelegate.java
中讀取 AndroidManifset.xml 內 meta-data
標籤,其中 io.flutter.app.android.SplashScreenUntilFirstFrame
標誌位如果為 ture ,就會啟動 Splash 畫面效果(類似IOS的啟動頁面)。
啟動時原生程式碼會讀取 android.R.attr.windowBackground
得到指定的 Drawable
, 用於顯示啟動閃屏效果,之後並且通過 flutterView.addFirstFrameListener
,在onFirstFrame
中移除閃屏。
好了,暫時都這裡了,有問題修改會或則補充的,後面再加上。
資源推薦
- Github : github.com/CarGuo
- 開源 Flutter 完整專案:github.com/CarGuo/GSYG…
- 開源 Flutter 單例子學習專案: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書專案:github.com/CarGuo/GSYF…
完整開源專案推薦:
文章
《Flutter完整開發實戰詳解(一、Dart語言和Flutter基礎)》
《Flutter完整開發實戰詳解(四、Redux、主題、國際化)》
《Flutter完整開發實戰詳解(六、 深入Widget原理)》
《Flutter完整開發實戰詳解(十、 深入圖片載入流程)》
《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》
《Flutter完整開發實戰詳解(十二、全面深入理解狀態管理設計)》