做爲系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,幫助咱們更好的去理解和開發。git
前文: 前文:github
這是一個膠水類。shell
混入其中( ̄. ̄)!xcode
是的,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、A二、B
都繼承它,同時再 print
以後執行 super()
操做。bash
從最後的輸入咱們能夠看出,A、A二、B
中的全部方法都被執行了,且只執行了一次,同時執行的順序也是和 with 的順序有關。若是你把下方代碼中 class A.a() 方法的 super 去掉,那麼你將看不到 B.a()
和 base a()
的輸出。app
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()
複製代碼
說了那麼多,那 Mixins 在 Flutter 中到底有什麼用呢?這時候咱們就要看 Flutter 中的「膠水類」: WidgetsFlutterBinding
。異步
WidgetsFlutterBinding 在 Flutter啓動時runApp
會被調用,做爲App的入口,它確定須要承擔各種的初始化以及功能配置,這種狀況下,Mixins 的做用就體現出來了。ide
從上圖咱們能夠看出, WidgetsFlutterBinding 自己是並無什麼代碼,主要是繼承了 BindingBase
,然後經過 with 黏上去的各種 Binding,這些 Binding 也都繼承了 BindingBase
。
看出來了沒,這裏每一個 Binding 均可以被單獨使用,也能夠被「黏」到 WidgetsFlutterBinding 中使用,這樣作的效果,是否是比起一級一級繼承的結構更加清晰了?
最後咱們打印下執行順序,以下圖因此,不出所料ヽ( ̄▽ ̄)ノ。
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操做,致使內存峯值太高。
如上圖所示,是圖片緩存相關的流程,而目前的拮据處理是經過:
更詳細的內容能夠閱讀文章本體,這裏爲何講到這個呢?是由於 限制緩存圖片的數量
這一項。
還記得 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的主線程)中執行的。
逃不開的需求。
一、首先咱們知道 Flutter 依然是一個 iOS/Android 工程。
二、Flutter經過在 BuildPhase 中添加 shell (xcode_backend.sh)來生成和嵌入App.framework 和 Flutter.framework 到 IOS。
三、Flutter經過 Gradle 引用 flutter.jar 和把編譯完成的二進制文件添加到 Android 中。
其中 Android 的編譯後二進制文件存在於 data/data/包名/app_flutter/flutter_assets/
下。作過 Android 的應該知道,這個路徑下是能夠很簡單更新的,因此你懂的  ̄ω ̄=。
⚠️注意,1.7.8 以後的版本,Android 下的 Flutter 已經編譯爲純 so 文件。
IOS?據我瞭解,貌似動態庫 framework 等引用是不能用熱更新的,除非你不須要審覈!
自此,第五篇終於結束了!(///▽///)
《Flutter完整開發實戰詳解(1、Dart語言和Flutter基礎)》
《Flutter完整開發實戰詳解(4、Redux、主題、國際化)》
《Flutter完整開發實戰詳解(6、 深刻Widget原理)》
《Flutter完整開發實戰詳解(10、 深刻圖片加載流程)》