Flutter完整開發(fā)實戰(zhàn)詳解(五、 深入探索)

作為系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,幫助我們更好的去理解和開發(fā)。

文章匯總地址:

Flutter 完整實戰(zhàn)實戰(zhàn)系列文章專欄

Flutter 番外的世界系列文章專欄

1、Mixins

混入其中( ̄. ̄)!,是的,F(xiàn)lutter 使用的是 Dart 支持 Mixin ,而 Mixin 能夠更好的解決多繼承中容易出現(xiàn)的問題,如:方法優(yōu)先順序混亂、參數(shù)沖突、類結(jié)構(gòu)變得復(fù)雜化等等。

Mixin 的定義解釋起來會比較繞,我們直接代碼從中出吧。如下代碼所示,在 Dart 中 with 就是用于 mixins。可以看出,class G extends B with A, A2 ,在執(zhí)行 G 的 a、b、c 方法后,輸出了 A2.a()、A.b() 、B.c() 。所以結(jié)論上簡單來說,就是相同方法被覆蓋了,并且 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()

接下來我們繼續(xù)修改下代碼。如下所示,我們定義了一個 Base 的抽象類,而A、A2、B 都繼承它,同時再 print 之后執(zhí)行 super() 操作。

從最后的輸入我們可以看出,A、A2、B中的所有方法都被執(zhí)行了,且只執(zhí)行了一次,同時執(zhí)行的順序也是和 with 的順序有關(guān)。如果你把下方代碼中 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會被調(diào)用,作為App的入口,它肯定需要承擔(dān)各類的初始化以及功能配置,這種情況下,Mixins 的作用就體現(xiàn)出來了。

image
image

從上圖我們可以看出, WidgetsFlutterBinding 本身是并沒有什么代碼,主要是繼承了 BindingBase,而后通過 with 黏上去的各類 Binding,這些 Binding 也都繼承了 BindingBase

看出來了沒,這里每個 Binding 都可以被單獨使用,也可以被“黏”到 WidgetsFlutterBinding 中使用,這樣做的效果,是不是比起一級一級繼承的結(jié)構(gòu)更加清晰了?

最后我們打印下執(zhí)行順序,如下圖所以,不出所料ヽ( ̄▽ ̄)?。

image

二、InheritedWidget

InheritedWidget 是一個抽象類,在 Flutter 中扮演者十分重要的角色,或者你并未直接使用過它,但是你肯定使用過和它相關(guān)的封裝。

image

如上圖所示,InheritedWidget 主要實現(xiàn)兩個方法:

  • 創(chuàng)建了 InheritedElement ,該 Element 屬于特殊 Element, 主要增加了將自身也添加到映射關(guān)系表 _inheritedWidgets【注1】,方便子孫 element 獲??;同時通過 notifyClients 方法來更新依賴。

  • 增加了 updateShouldNotify 方法,當(dāng)方法返回 true 時,那么依賴該 Widget 的實例就會更新。

所以我們可以簡單理解:InheritedWidget 通過 InheritedElement 實現(xiàn)了由下往上查找的支持(因為自身添加到 _inheritedWidgets),同時具備更新其子孫的功能。

注1:每個 Element 都有一個 _inheritedWidgets ,它是一個 HashMap<Type, InheritedElement>,它保存了上層節(jié)點中出現(xiàn)的 InheritedWidget 與其對應(yīng) element 的映射關(guān)系。

image

接著我們看 BuildContext,如上圖,BuildContext 其實只是接口, Element 實現(xiàn)了它。InheritedElementElement 的子類,所以每一個 InheritedElement 實例是一個 BuildContext 實例。同時我們?nèi)粘J褂弥袀鬟f的 BuildContext 也都是一個 Element 。

所以當(dāng)我們遇到需要共享 State 時,如果逐層傳遞 state 去實現(xiàn)共享會顯示過于麻煩,那么了解了上面的 InheritedWidget 之后呢?

是否將需要共享的 State,都放在一個 InheritedWidget 中,然后在使用的 widget 中直接取用就可以呢?答案是肯定的!所以如下方這類代碼:通常如 焦點、主題色、多語言、用戶信息 等都屬于 App 內(nèi)的全局共享數(shù)據(jù),他們都會通過 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 設(shè)置主題數(shù)據(jù),通過 Theme.of(context) 就可以獲取到主題數(shù)據(jù)并綁定使用。當(dāng) MaterialApp 的主題數(shù)據(jù)變化時,對應(yīng)的 Widget 顏色也會發(fā)生變化,這是為什么呢(?`?Д?′)!!?

  ///添加主題
  new MaterialApp(
      theme: ThemeData.dark()
  );
  
  ///使用主題色
  new Container( color: Theme.of(context).primaryColor,

通過源碼一層層查找,可以發(fā)現(xiàn)這樣的嵌套: MaterialApp -> AnimatedTheme -> Theme -> _InheritedTheme extends InheritedWidget ,所以通過 MaterialApp 作為入口,其實就是嵌套在 InheritedWidget 下。

image

如上圖所示,通過 Theme.of(context) 獲取到的主題數(shù)據(jù),其實是通過 context.inheritFromWidgetOfExactType(_InheritedTheme) 去獲取的,而 Element 中實現(xiàn)了 BuildContextinheritFromWidgetOfExactType 方法,如下所示:

image

那么,還記得上面說的 _inheritedWidgets 嗎?既然 InheritedElement 已經(jīng)存在于 _inheritedWidgets 中,拿出來用就對了。

前文:InheritedWidget 內(nèi)的 InheritedElement ,該 Element 屬于特殊 Element, 主要增加了將自身也添加到映射關(guān)系表 _inheritedWidgets

最后,如下圖所示,在 InheritedElement 中,notifyClients 通過 InheritedWidgetupdateShouldNotify 方法判斷是否更新,比如在 Theme_InheritedTheme 是:

bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
image

所以本質(zhì)上 Theme、Redux 、 Scope Model、Localizations 的核心都是 InheritedWidget。

三、內(nèi)存

最近閑魚技術(shù)發(fā)布了 《Flutter之禪 內(nèi)存優(yōu)化篇》 ,文中對于 Flutter 的內(nèi)存做了深度的探索,其中有一個很有趣的發(fā)現(xiàn)是:

  • Flutter 中 ImageCache 緩存的是 ImageStream 對象,也就是緩存的是一個異步加載的圖片的對象。
  • 在圖片加載解碼完成之前,無法知道到底將要消耗多少內(nèi)存。
  • 所以容易產(chǎn)生大量的IO操作,導(dǎo)致內(nèi)存峰值過高。
圖片來自閑魚技術(shù)

如上圖所示,是圖片緩存相關(guān)的流程,而目前的拮據(jù)處理是通過:

  • 在頁面不可見的時候沒必要發(fā)出多余的圖片
  • 限制緩存圖片的數(shù)量
  • 在適當(dāng)?shù)臅r候CG

更詳細(xì)的內(nèi)容可以閱讀文章本體,這里為什么講到這個呢?是因為 限制緩存圖片的數(shù)量 這一項。

還記得 WidgetsFlutterBinding 這個膠水類嗎?其中Mixins 了 PaintingBinding 如下圖所示,被"黏“上去的這個 binding 就是負(fù)責(zé)圖片緩存

image

PaintingBinding 內(nèi)有一個 ImageCache 對象,該對象全局一個單例的,同時再圖片加載時的 ImageProvider 所使用,所以設(shè)置圖片緩存大小如下:

//緩存?zhèn)€數(shù) 100
PaintingBinding.instance.imageCache.maximumSize=100;
//緩存大小 50m
PaintingBinding.instance.imageCache.maximumSizeBytes= 50 << 20;

四、線程

在閑魚技術(shù)的 深入理解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 和原生端通信的應(yīng)該知道,通過 Platform Channel 通信的兩端就是 Platform Task RunnerUI Task Runner,這里主要總結(jié)起來是:

  • 因為 Platform Task Runner 本來就是原生的主線程,所以盡量不要在 Platform 端執(zhí)行耗時操作。

  • 因為Platform Channel并非是線程安全的,所以消息處理結(jié)果回傳到Flutter端時,需要確?;卣{(diào)函數(shù)是在Platform Thread(也就是Android和iOS的主線程)中執(zhí)行的。

圖片來自閑魚技術(shù)

五、熱更新

逃不開的需求。

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

  • 2、Flutter通過在 BuildPhase 中添加 shell (xcode_backend.sh)來生成和嵌入App.frameworkFlutter.framework 到 IOS。

  • 3、Flutter通過 Gradle 引用 flutter.jar 和把編譯完成的二進(jìn)制文件添加到 Android 中。

其中 Android 的編譯后二進(jìn)制文件存在于 data/data/包名/app_flutter/flutter_assets/下。做過 Android 的應(yīng)該知道,這個路徑下是可以很簡單更新的,所以你懂的  ̄ω ̄=。

??注意,1.7.8 之后的版本,Android 下的 Flutter 已經(jīng)編譯為純 so 文件。

IOS?據(jù)我了解,貌似動態(tài)庫 framework 等引用是不能用熱更新的,除非你不需要審核!

自此,第五篇終于結(jié)束了!(///▽///)

資源推薦

完整開源項目推薦:
我們還會再見嗎?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容