深刻理解 Flutter 框架層次結構

做者: Frederik Schweiger
連接 : The Layer Cake算法

Flutter 是一個很是優秀的跨平臺開發框架,基於 Flutter 咱們能夠用不多的代碼快速的開發出界面精美的 APP ,同時熱重載機制也極大的提升了咱們的開發效率,而且基於 Flutter 開發的 APP 運行起來也是如絲般順滑,可以達到 120 fps。那麼,你對此有沒有過疑問,Flutter 是怎麼這麼快的?這裏面有什麼奧祕嗎?或者,更直接一點,Flutter 究竟是怎麼工做的?接下來的內容但願可以給你答案。編程

你確定早就據說過了,Flutter 中萬物皆是 Widget,你的 app 是一個 widget,text 是一個widget,一個 widget 外面包裹的 padding 也是一個 widget ,甚至手勢識別的功能也是經過 widget 來實現的。固然,這不是所有的真相,若是我告訴你,確實經過使用 widget 讓咱們的開發變得簡單高效,可是咱們可以建立一個 Flutter app 不使用哪怕是一個 widget 呢?接下來,就讓我稍微深刻的探索一下這個框架吧。canvas

1、框架入門

也許你已經在一些相似於‘Flutter入門介紹’的文章中對Flutter有了大體的瞭解,可是你可能尚未準備好去理解這些層次結構背後的那些概念和原理,也許你也曾和我同樣,呆呆的看着這張圖片卻得不到任何東西,不用擔憂,我會幫助你來理解,咱們仍是先來看一下這個圖片吧。緩存

Flutter 框架由許多抽象的層級組成,在這些層級的最頂端是咱們常常用到的 Material 和 Cupertino Widget,這下面是封裝更通用組件的 Widget 層。一般狀況下,你會發現你僅僅使用這兩層中的 Widget 就夠用了,而且目前你所能看到或者使用的,也基本都來自這兩層,好比頁面腳手架組件 Scaffold 和 FlaotingActionButton 來自 Meterial 包,Column 和 GestureDector 來自 widgets 層。微信

在Widget層下面是 Rendering 層。Rendering層簡化了佈局和繪製過程。它是 dart:ui 層的抽象化。dart:ui是框架的最底層,它負責處理與 Flutter Engine 的通訊。app

簡而言之,層級越高,封裝度越高,咱們使用越方便,而後層級越低使用起來可能更加自由,控制粒度更精細,固然也會更加複雜。框架

一、dart:ui 層

dart:ui library 暴露了最底層的服務,Flutter 框架基於這一層來構建應用程序,好比輸入驅動、,繪製文字,佈局和渲染系統等。less

因此你能夠僅僅經過使用 dart:ui庫中的類(例如Canvas、Paint和TextField)來構建一個Flutter App。可是若是你對於直接在 canvas 上繪製比較熟悉,你就知道了繪製不只僅是繪製一個簡筆畫圖像,首先繪製管理就讓人頭疼,同時還要考慮管理和組織如何佈局,而且當你觸摸了你 app 上的元素,你還要可以計算出來並響應(hit-testing)。佈局

那這究竟意味着什麼呢?意味着你必需要精確的計算出你佈局中全部元素的座標,而後還要實現繪製到屏幕的功能,同時可以對外界的輸入事件(觸摸屏幕)可以捕獲到作出響應。對於屏幕上繪製的每一幀,你都要實現這些功能,經過這種方式來開發的 APP,若是隻是在一個藍色區塊上顯示一個文本這種簡單的界面應用,可能還能實現,可是若是是一個購物應用程序或者一個遊戲,這可能就是不可能實現的了,更不要說本身去處理動畫和實現複雜精美的界面效果了。我只是把個人經驗告訴你,若是用這種方式開發這將是咱們開發者的噩夢。post

二、 Rendering 層

Rendering 層,也就是 Flutter 的渲染樹(Flutter rendering) 。Widgets 層 使用 RenderObject 來實現了佈局和繪製。一般狀況下,若是你是使用自定義 RenderBox 來實現一些特殊效果,用到的就是這一層提供的類,可是大多數狀況下,咱們和這層的交互都是當咱們遇到了一個佈局問題而 debug 時。

Rendering 層是 dart:ui 層上的第一個抽象層,同時這層替咱們作了所有發的複雜的數學計算工做(好比持續追蹤計算元素的座標值),Rendering 層是經過 RenderObjects 來完成工做的。你能夠把 RenderObjects 想象成汽車的發動機,咱們的 app 可以真的的顯示到屏幕上就是經過RenderObjects 來完成的,由 RenderObjects 所組成的渲染樹會被 Flutter 分層佈局並渲染到屏幕上。固然爲了優化這些複雜的流程,Flutter 也使用了一些智能算法,會緩存每次的計算結果,每次都只更新很小的部分。

一般狀況下,Flutter 使用 RenderBox 而不是 RenderObject,這是由於 Flutter 開發者發現,簡單的盒約束佈局模型也可以很好的完成工做構建精美的界面,想象一下,每一個 widget 都有本身盒子約束模型,在一個盒子當中,計算了其約束關係和大小等,而後把這個 widget 加入到一個已經計算好盒子系統當中,這裏面,若是有一個 widget 發生了改變,系統只須要從新計算這個 widget 所在盒子的約束關係便可。

三、 Widgets 層

Flutter Widgets 框架層

Widgets 這層是頗有意思的一層,這層給咱們提供了能夠直接使用的 UI 組件,這裏面全部的組件均可以分紅三類,每一類都有對應的 RenderObject 來處理。

  • 佈局相關(layout)

好比 Column 和 Row ,這兩個 Widget 能夠幫咱們輕鬆的實現水平或者垂直排列組件。

  • 繪製相關(Parning)

好比 Text 和 Image ,這類的組件能夠幫咱們展現或者說渲染一些內容到屏幕上。

  • 手勢檢測相關 好比 GestureDetector 組件,這個組件可以檢測到屏幕點擊或者滑動事件。

一般來講,咱們都是經過使用這些基礎組件來組成咱們本身的組件或者組件樹,好比咱們可使用 GestureDetector 包裹 Container 來監聽點擊事件從而實現一個按鈕組件。

四、 Material & Cupertino 層

這層的 Widget 是 Widgets 層的封裝,只不過是實現了 Material 和 Cupetino 風格的 Widget 提供給咱們使用。

總的來講,Flutter 框架的設計思想就是抽象和封裝,這可讓咱們開發者開發更加方便。這層是 Flutter 框架的第四層,這裏面的組件都是 Flutter 框架封裝好的提供給咱們使用,Material 是材料設計風格的而 Cupetino 是 iOS 風格的,好比 AlertDialog、Switch 和 FloatingActionButton ,若是你是 iOS 開發者,那麼你可使用 CupertinoAlertDialog 、CupertinoButton 和 CupertinoSwitch ,這些組件你可能看起來更熟悉。

Flutter爲了減小開發者的負擔,建立了這個擁有Material和Cupertino風格的Widgets層。

2、綜合分析

思考一下,RenderObject 是如何與 Widgets 聯繫起來的呢? Flutter是如何建立佈局的?Element又是什麼呢?

上面已經對框架結構進行了簡單的介紹,接下來看一下真的的實踐,好比下面簡單的 控件樹。

ps: 咱們實際開發中使用的 widget,好比 Text,都是由許多其餘的 Widget 來組成的,爲了演示和講解,這裏用抽象的 Widget (SimpleContainer 和 SimpleText) 來代替 。

咱們構建的這個 APP 是很是簡單的。它由三個Stateless Widget組成:SimpleApp、SimpleContainer、SimpleText。當咱們調用 Flutter 的 runApp() 方法會發生什麼呢?

當 Flutter 運行 runApp() 以後,這背後將會運行下面一些列事情:

一、Flutter 將會構建包含這三個 Widget 的 Widgets 樹。

二、Flutter 遍歷 Widget 樹,經過 Widget 裏面的 createElement 方法來建立其關聯的 Element 對象,而後將這些對象組建成 Element 樹。

三、最後 ,Element 會調用 createRenderObject() 方法來建立想關聯的 RenderObject 對象,並組建第三棵樹,RenderObject 樹。

當 Flutter 建立好了三個對應的樹以後,能夠用以下圖片來描述:

此時,Flutter 建立了三個不一樣的樹,一個是 Widget 樹、一個是 Element 樹,一個是 RenderObject 樹,而且,每一個 Element 都會持有對應的 Widget 和 RenderObject 的引用。

接下來看一下什麼是 RenderObject 。

RenderObject 裏面實現了渲染其對應的 widget 的全部邏輯,同時 RenderObject 對象的實例化是一個很是重量級的操做,不只如此,這裏面還要實現佈局、渲染以及手勢檢測。所以,一個 RenderObject 對象的實例應該儘量的緩存到內存中,直到不用以後再回收它們。

接下來就該說到 Element 了,Element 其實就像是 RenderObject 和 Widget 之間的粘合劑,Widtet 是不可變的,而 RenderObejct 是可變的。Element 能夠看做是一個 Widget 在 Widget 樹中特定位置的一個實例,Element 也同時持有其關聯的 Widget 和 RenderObject 的引用。

爲何使用三個樹而不是一個樹呢?簡而言之是爲了性能。當 Widget 樹改變的時候,Flutter使用 Element 樹來比較新的 Widget 樹和已經建立的 RenderObject 樹。當一個 Widget 的類型和上一次的 Widget 類型相同,那麼 Flutter 就不會從新建立 RenderObject 了(太耗性能),只要更新一下其變化的參數配置就好了。因爲 Flutter 中 widget 的建立和銷燬都是輕量級的操做,所以用 widget 來作作爲配置參數來描述當前 app 的狀態再好不過了。而 RenderObject 的建立是一個重量級的操做,所以 RenderObejct 要儘量的複用。

然而,在Flutter 框架中,Element是被抽離開來的,因此咱們不須要常常和它們打交道。每一個Widget的 build(BuildContext context)方法中傳遞的 context 就是實現了BuildContext 接口的 Element,這也就是爲何相同類別的單個Widget 會不一樣的緣由。

3、深刻分析

由於 Widget 是不可變的,當某個 Widget 的配置改變的時候,整個 Widget 樹都須要被重建。例如當咱們把咱們上面代碼裏面的 Container 的顏色爲紅色的時候,Flutter 框架就會觸發重建整個 Widget 樹的操做。而後在 Element 的幫助下,Flutter 會比較新的 Widget 樹中的第一個 Widget 和 RenderObject 樹中第一個 RenderObject 。接下來比較Widget樹中第二個 Widget 和RenderObject 樹中第二個 RenderObject,以此類推,直到 Widget 樹和 RendObject 樹比較完成。

Flutter 會遵循一個最基本的原則:判斷新建立的 Widget 和老的 Widget 是不是同一個類型。若是不是同一個類型,那就把 Widget、Element、RenderObject 分別從它們的樹(包括它們的子樹)上移除,而後建立新的對象。若是是一個類型,那就僅僅修改 RenderObject中的配置,而後接着遍歷其餘對象。

在咱們的例子中,SimpleApp Widget 是和原來同樣的類型,它的配置也是和原來的 SimpleAppRender 同樣,因此什麼都不會發生。下一個 item 在 Widget 樹中是 SimpleContainer Widget,它的類型和原來是同樣的,可是它的顏色變化了,RenderObject的配置發生變化了。由於SimContainer 仍然須要一個SimpleContainerRender 來渲染,所以 Flutter 只是更新了SimpleContainerRender的顏色屬性,而後要求它從新渲染。其餘的對象保持不變。

當 widget 樹從新建立以後的狀態以下圖所示(須要注意的是,Element 和 RenderObject 依然是同一個實例對象)

這個過程是很是快的,由於Flutter 很是擅長建立那些輕量級的 Widgets。那些重量級的 RenderObject 則是保持不變,直到與其相對應類型的 Widget 從 Widget 樹中被移除。那若是Widget的類型發生改變了會發生什麼?

好比將 SimpleText 替換爲 SimpleButton

和上面同樣,Flutter 會重建 Widget 樹,並遍歷這棵樹,而後比較 Widget 的類型是否和 RenderObject 中的對象對應。

此時三顆樹的狀態以下所示,改變類型的 Widget 對應的 Element 和 SimpleTextRender 已經被移除了

由於SimpleButton 的類型與 Element 樹中相對應位置的 Element 的類型不一樣,Flutter將會從另外兩棵樹上刪除對應的 Element 和相對應的 SimpleTextRender。而後Flutter 將會重建與 SimpleButton 相對應的 Element 和 RenderObject 對象實例。

最終的狀態以下

而後新的 RenderObject 樹已經被重建,而後從新佈局並繪製在屏幕上面。在這裏面,Flutter 作了大量優化工做而且其使用的緩存策略,可以讓咱們沒必要手動的對這些對象進行管理。

4、總結

如今你應該對 Flutter 是如何工做的,爲何可以渲染複雜的佈局而且運行的還很流暢有了必定的瞭解,這裏面尚未講到 State,Flutter 引入了 State 來提高了這個過程的總體性能,今天暫時不對此進行討論了,但願這篇文章可以幫助你理解 Flutter 框架。


推薦閱讀

Widget、RenderObject 與 Element


歡迎關注「Flutter 編程開發」微信公衆號 。

相關文章
相關標籤/搜索