Flutter完整開發實戰詳解(5、 深刻探索)

做爲系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,幫助咱們更好的去理解和開發。git

前文: 前文:github

1、WidgetsFlutterBinding

這是一個膠水類。shell

一、Mixins

混入其中( ̄. ̄)!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()

複製代碼

二、WidgetsFlutterBinding

說了那麼多,那 Mixins 在 Flutter 中到底有什麼用呢?這時候咱們就要看 Flutter 中的「膠水類」: WidgetsFlutterBinding異步

WidgetsFlutterBinding 在 Flutter啓動時runApp會被調用,做爲App的入口,它確定須要承擔各種的初始化以及功能配置,這種狀況下,Mixins 的做用就體現出來了。ide

從上圖咱們能夠看出, WidgetsFlutterBinding 自己是並無什麼代碼,主要是繼承了 BindingBase,然後經過 with 黏上去的各種 Binding,這些 Binding 也都繼承了 BindingBase

看出來了沒,這裏每一個 Binding 均可以被單獨使用,也能夠被「黏」到 WidgetsFlutterBinding 中使用,這樣作的效果,是否是比起一級一級繼承的結構更加清晰了?

最後咱們打印下執行順序,以下圖因此,不出所料ヽ( ̄▽ ̄)ノ。

2、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 實現了它。InheritedElementElement 的子類,因此每個 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 中實現了 BuildContextinheritFromWidgetOfExactType 方法,以下所示:

那麼,還記得上面說的 _inheritedWidgets 嗎?既然 InheritedElement 已經存在於 _inheritedWidgets 中,拿出來用就對了。

前文:InheritedWidget 內的 InheritedElement ,該 Element 屬於特殊 Element, 主要增長了將自身也添加到映射關係表 _inheritedWidgets

最後,以下圖所示,在 InheritedElement 中,notifyClients 經過 InheritedWidgetupdateShouldNotify 方法判斷是否更新,好比在 Theme_InheritedTheme 是:

bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
複製代碼

因此本質上 Theme、Redux 、 Scope Model、Localizations 的核心都是 InheritedWidget

3、內存

最近閒魚技術發佈了 《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;
複製代碼

4、線程

在閒魚技術的 深刻理解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 RunnerUI Task Runner,這裏主要總結起來是:

  • 由於 Platform Task Runner 原本就是原生的主線程,因此儘可能不要在 Platform 端執行耗時操做。

  • 由於Platform Channel並不是是線程安全的,因此消息處理結果回傳到Flutter端時,須要確保回調函數是在Platform Thread(也就是Android和iOS的主線程)中執行的。

圖片來自閒魚技術

5、熱更新

逃不開的需求。

  • 一、首先咱們知道 Flutter 依然是一個 iOS/Android 工程。

  • 二、Flutter經過在 BuildPhase 中添加 shell (xcode_backend.sh)來生成和嵌入App.frameworkFlutter.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完整開發實戰詳解(2、 快速開發實戰篇)》

《Flutter完整開發實戰詳解(3、 打包與填坑篇)》

《Flutter完整開發實戰詳解(4、Redux、主題、國際化)》

《Flutter完整開發實戰詳解(5、 深刻探索)》

《Flutter完整開發實戰詳解(6、 深刻Widget原理)》

《Flutter完整開發實戰詳解(7、 深刻佈局原理)》

《Flutter完整開發實戰詳解(8、 實用技巧與填坑)》

《Flutter完整開發實戰詳解(9、 深刻繪製原理)》

《Flutter完整開發實戰詳解(10、 深刻圖片加載流程)》

《Flutter完整開發實戰詳解(11、全面深刻理解Stream)》

《跨平臺項目開源項目推薦》

《移動端跨平臺開發的深度解析》

《React Native 的將來與React Hooks》

咱們還會再見嗎?
相關文章
相關標籤/搜索