作為系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,幫助我們更好的去理解和開發。
文章彙總地址:
1、Mixins
混入其中( ̄. ̄)!,是的,Flutter 使用的是 Dart 支援 Mixin ,而 Mixin 能夠更好的解決多繼承中容易出現的問題,如:方法優先順序混亂、引數衝突、類結構變得複雜化等等。
Mixin 的定義解釋起來會比較繞,我們直接程式碼從中出吧。如下程式碼所示,在 Dart 中 with
就是用於 mixins。可以看出,class G extends B with A, A2
,在執行 G 的 a、b、c 方法後,輸出了 A2.a()、A.b() 、B.c()
。所以結論上簡單來說,就是相同方法被覆蓋了,並且 with 後面的會覆蓋前面的。
class A {
a() {
print("A.a()");
}
b() {
print("A.b()");
}
}
class A2 {
a() {
print("A2.a()");
}
}
class B {
a() {
print("B.a()");
}
b() {
print("B.b()");
}
c() {
print("B.c()");
}
}
class G extends B with A, A2 {
}
testMixins() {
G t = new G();
t.a();
t.b();
t.c();
}
/// ***********************輸出***********************
///I/flutter (13627): A2.a()
///I/flutter (13627): A.b()
///I/flutter (13627): B.c()
複製程式碼
接下來我們繼續修改下程式碼。如下所示,我們定義了一個 Base
的抽象類,而A、A2、B
都繼承它,同時再 print
之後執行 super()
操作。
從最後的輸入我們可以看出,A、A2、B
中的所有方法都被執行了,且只執行了一次,同時執行的順序也是和 with 的順序有關。如果你把下方程式碼中 class A.a() 方法的 super 去掉,那麼你將看不到 B.a()
和 base a()
的輸出。
abstract class Base {
a() {
print("base a()");
}
b() {
print("base b()");
}
c() {
print("base c()");
}
}
class A extends Base {
a() {
print("A.a()");
super.a();
}
b() {
print("A.b()");
super.b();
}
}
class A2 extends Base {
a() {
print("A2.a()");
super.a();
}
}
class B extends Base {
a() {
print("B.a()");
super.a();
}
b() {
print("B.b()");
super.b();
}
c() {
print("B.c()");
super.c();
}
}
class G extends B with A, A2 {
}
testMixins() {
G t = new G();
t.a();
t.b();
t.c();
}
///I/flutter (13627): A2.a()
///I/flutter (13627): A.a()
///I/flutter (13627): B.a()
///I/flutter (13627): base a()
///I/flutter (13627): A.b()
///I/flutter (13627): B.b()
///I/flutter (13627): base b()
///I/flutter (13627): B.c()
///I/flutter (13627): base c()
複製程式碼
2、WidgetsFlutterBinding
說了那麼多,那 Mixins 在 Flutter 中到底有什麼用呢?這時候我們就要看 Flutter 中的“膠水類”: WidgetsFlutterBinding
。
WidgetsFlutterBinding 在 Flutter啟動時runApp
會被呼叫,作為App的入口,它肯定需要承擔各類的初始化以及功能配置,這種情況下,Mixins 的作用就體現出來了。
從上圖我們可以看出, WidgetsFlutterBinding 本身是並沒有什麼程式碼,主要是繼承了 BindingBase
,而後通過 with 黏上去的各類 Binding,這些 Binding 也都繼承了 BindingBase
。
看出來了沒,這裡每個 Binding 都可以被單獨使用,也可以被“黏”到 WidgetsFlutterBinding 中使用,這樣做的效果,是不是比起一級一級繼承的結構更加清晰了?
最後我們列印下執行順序,如下圖所以,不出所料ヽ( ̄▽ ̄)ノ。
二、InheritedWidget
InheritedWidget 是一個抽象類,在 Flutter 中扮演者十分重要的角色,或者你並未直接使用過它,但是你肯定使用過和它相關的封裝。
如上圖所示,InheritedWidget 主要實現兩個方法:
-
建立了
InheritedElement
,該 Element 屬於特殊 Element, 主要增加了將自身也新增到對映關係表_inheritedWidgets
【注1】,方便子孫 element 獲取;同時通過notifyClients
方法來更新依賴。 -
增加了
updateShouldNotify
方法,當方法返回 true 時,那麼依賴該 Widget 的例項就會更新。
所以我們可以簡單理解:InheritedWidget 通過 InheritedElement
實現了由下往上查詢的支援(因為自身新增到 _inheritedWidgets
),同時具備更新其子孫的功能。
注1:每個 Element 都有一個
_inheritedWidgets
,它是一個HashMap<Type, InheritedElement>
,它儲存了上層節點中出現的 InheritedWidget 與其對應 element 的對映關係。
接著我們看 BuildContext,如上圖,BuildContext 其實只是介面, Element 實現了它。InheritedElement
是 Element 的子類,所以每一個 InheritedElement 例項是一個 BuildContext 例項。同時我們日常使用中傳遞的 BuildContext 也都是一個 Element 。
所以當我們遇到需要共享 State 時,如果逐層傳遞 state 去實現共享會顯示過於麻煩,那麼瞭解了上面的 InheritedWidget 之後呢?
是否將需要共享的 State,都放在一個 InheritedWidget 中,然後在使用的 widget 中直接取用就可以呢?答案是肯定的!所以如下方這類程式碼:通常如 焦點、主題色、多語言、使用者資訊 等都屬於 App 內的全域性共享資料,他們都會通過 BuildContext(InheritedElement) 獲取。
///收起鍵盤
FocusScope.of(context).requestFocus(new FocusNode());
/// 主題色
Theme.of(context).primaryColor
/// 多語言
Localizations.of(context, GSYLocalizations)
/// 通過 Redux 獲取使用者資訊
StoreProvider.of(context).userInfo
/// 通過 Redux 獲取使用者資訊
StoreProvider.of(context).userInfo
/// 通過 Scope Model 獲取使用者資訊
ScopedModel.of<UserInfo>(context).userInfo
複製程式碼
綜上所述,我們從先 Theme
入手。
如下方程式碼所示,通過給 MaterialApp
設定主題資料,通過 Theme.of(context)
就可以獲取到主題資料並繫結使用。當 MaterialApp
的主題資料變化時,對應的 Widget 顏色也會發生變化,這是為什麼呢(キ`゚Д゚´)!!?
///新增主題
new MaterialApp(
theme: ThemeData.dark()
);
///使用主題色
new Container( color: Theme.of(context).primaryColor,
複製程式碼
通過原始碼一層層查詢,可以發現這樣的巢狀: MaterialApp -> AnimatedTheme -> Theme -> _InheritedTheme extends InheritedWidget
,所以通過 MaterialApp
作為入口,其實就是巢狀在 InheritedWidget 下。
如上圖所示,通過 Theme.of(context)
獲取到的主題資料,其實是通過 context.inheritFromWidgetOfExactType(_InheritedTheme)
去獲取的,而 Element 中實現了 BuildContext 的 inheritFromWidgetOfExactType
方法,如下所示:
那麼,還記得上面說的 _inheritedWidgets
嗎?既然 InheritedElement
已經存在於 _inheritedWidgets 中,拿出來用就對了。
前文:InheritedWidget 內的
InheritedElement
,該 Element 屬於特殊 Element, 主要增加了將自身也新增到對映關係表 _inheritedWidgets
最後,如下圖所示,在 InheritedElement 中,notifyClients
通過 InheritedWidget
的 updateShouldNotify
方法判斷是否更新,比如在 Theme的 _InheritedTheme
是:
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
複製程式碼
所以本質上 Theme、Redux 、 Scope Model、Localizations 的核心都是
InheritedWidget
。
三、記憶體
最近閒魚技術釋出了 《Flutter之禪 記憶體優化篇》 ,文中對於 Flutter 的記憶體做了深度的探索,其中有一個很有趣的發現是:
- Flutter 中 ImageCache 快取的是 ImageStream 物件,也就是快取的是一個非同步載入的圖片的物件。
- 在圖片載入解碼完成之前,無法知道到底將要消耗多少記憶體。
- 所以容易產生大量的IO操作,導致記憶體峰值過高。
如上圖所示,是圖片快取相關的流程,而目前的拮据處理是通過:
- 在頁面不可見的時候沒必要發出多餘的圖片
- 限制快取圖片的數量
- 在適當的時候CG
更詳細的內容可以閱讀文章本體,這裡為什麼講到這個呢?是因為 限制快取圖片的數量
這一項。
還記得 WidgetsFlutterBinding
這個膠水類嗎?其中Mixins 了 PaintingBinding
如下圖所示,被"黏“上去的這個 binding 就是負責圖片快取
在 PaintingBinding
內有一個 ImageCache
物件,該物件全域性一個單例的,同時再圖片載入時的 ImageProvider
所使用,所以設定圖片快取大小如下:
//快取個數 100
PaintingBinding.instance.imageCache.maximumSize=100;
//快取大小 50m
PaintingBinding.instance.imageCache.maximumSizeBytes= 50 << 20;
複製程式碼
四、執行緒
在閒魚技術的 深入理解Flutter Platform Channel 中有講到:Flutter中有四大執行緒,Platform Task Runner 、UI Task Runner、GPU Task Runner 和 IO Task Runner。
其中 Platform Task Runner
也就是 Android 和 iOS 的主執行緒,而 UI Task Runner
就是Flutter的 UI 執行緒。
如下圖,如果做過 Flutter 中 Dart 和原生端通訊的應該知道,通過 Platform Channel
通訊的兩端就是 Platform Task Runner
和 UI Task Runner
,這裡主要總結起來是:
-
因為 Platform Task Runner 本來就是原生的主執行緒,所以儘量不要在 Platform 端執行耗時操作。
-
因為Platform Channel並非是執行緒安全的,所以訊息處理結果回傳到Flutter端時,需要確保回撥函式是在Platform Thread(也就是Android和iOS的主執行緒)中執行的。
五、熱更新
逃不開的需求。
-
1、首先我們知道 Flutter 依然是一個 iOS/Android 工程。
-
2、Flutter通過在 BuildPhase 中新增 shell (xcode_backend.sh)來生成和嵌入App.framework 和 Flutter.framework 到 IOS。
-
3、Flutter通過 Gradle 引用 flutter.jar 和把編譯完成的二進位制檔案新增到 Android 中。
其中 Android 的編譯後二進位制檔案存在於 data/data/包名/app_flutter/flutter_assets/
下。做過 Android 的應該知道,這個路徑下是可以很簡單更新的,所以你懂的  ̄ω ̄=。
⚠️注意,1.7.8 之後的版本,Android 下的 Flutter 已經編譯為純 so 檔案。
IOS?據我瞭解,貌似動態庫 framework 等引用是不能用熱更新的,除非你不需要稽核!
自此,第五篇終於結束了!(///▽///)
資源推薦
- Github : github.com/CarGuo/
- 開源 Flutter 完整專案:github.com/CarGuo/GSYG…
- 開源 Flutter 多案例學習型專案: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書專案:github.com/CarGuo/GSYF…