作者: 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 呢?接下來,就讓我稍微深入的探索一下這個框架吧。
一、框架入門
也許你已經在一些類似於‘Flutter入門介紹’的文章中對Flutter有了大致的瞭解,但是你可能還沒有準備好去理解這些層次結構背後的那些概念和原理,也許你也曾和我一樣,呆呆的看著這張圖片卻得不到任何東西,不用擔心,我會幫助你來理解,我們還是先來看一下這個圖片吧。
Flutter 框架由許多抽象的層級組成,在這些層級的最頂端是我們經常用到的 Material 和 Cupertino Widget,這下面是封裝更通用元件的 Widget 層。通常情況下,你會發現你僅僅使用這兩層中的 Widget 就夠用了,並且目前你所能看到或者使用的,也基本都來自這兩層,比如頁面腳手架元件 Scaffold 和 FlaotingActionButton 來自 Meterial 包,Column 和 GestureDector 來自 widgets 層。
在Widget層下面是 Rendering 層。Rendering層簡化了佈局和繪製過程。它是 dart:ui 層的抽象化。dart:ui是框架的最底層,它負責處理與 Flutter Engine 的通訊。
簡而言之,層級越高,封裝度越高,我們使用越方便,然後層級越低使用起來可能更加自由,控制粒度更精細,當然也會更加複雜。
1、dart:ui 層
dart:ui library 暴露了最底層的服務,Flutter 框架基於這一層來構建應用程式,比如輸入驅動、,繪製文字,佈局和渲染系統等。
所以你可以僅僅通過使用 dart:ui庫中的類(例如Canvas、Paint和TextField)來構建一個Flutter App。但是如果你對於直接在 canvas 上繪製比較熟悉,你就知道了繪製不僅僅是繪製一個簡筆畫影像,首先繪製管理就讓人頭疼,同時還要考慮管理和組織如何佈局,並且當你觸控了你 app 上的元素,你還要能夠計算出來並響應(hit-testing)。
那這究竟意味著什麼呢?意味著你必須要精確的計算出你佈局中所有元素的座標,然後還要實現繪製到螢幕的功能,同時能夠對外界的輸入事件(觸控螢幕)能夠捕獲到做出響應。對於螢幕上繪製的每一幀,你都要實現這些功能,通過這種方式來開發的 APP,如果只是在一個藍色區塊上顯示一個文字這種簡單的介面應用,可能還能實現,但是如果是一個購物應用程式或者一個遊戲,這可能就是不可能實現的了,更不要說自己去處理動畫和實現複雜精美的介面效果了。我只是把我的經驗告訴你,如果用這種方式開發這將是我們開發者的噩夢。
2、 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 所在盒子的約束關係即可。
3、 Widgets 層
Flutter Widgets 框架層
Widgets 這層是很有意思的一層,這層給我們提供了可以直接使用的 UI 元件,這裡面所有的元件都可以分成三類,每一類都有對應的 RenderObject 來處理。
- 佈局相關(layout)
比如 Column 和 Row ,這兩個 Widget 可以幫我們輕鬆的實現水平或者垂直排列元件。
- 繪製相關(Parning)
比如 Text 和 Image ,這類的元件可以幫我們展示或者說渲染一些內容到螢幕上。
- 手勢檢測相關 比如 GestureDetector 元件,這個元件能夠檢測到螢幕點選或者滑動事件。
通常來說,我們都是通過使用這些基礎元件來組成我們自己的元件或者元件樹,比如我們可以使用 GestureDetector 包裹 Container 來監聽點選事件從而實現一個按鈕元件。
4、 Material & Cupertino 層
這層的 Widget 是 Widgets 層的封裝,只不過是實現了 Material 和 Cupetino 風格的 Widget 提供給我們使用。
總的來說,Flutter 框架的設計思想就是抽象和封裝,這可以讓我們開發者開發更加方便。這層是 Flutter 框架的第四層,這裡面的元件都是 Flutter 框架封裝好的提供給我們使用,Material 是材料設計風格的而 Cupetino 是 iOS 風格的,比如 AlertDialog、Switch 和 FloatingActionButton ,如果你是 iOS 開發者,那麼你可以使用 CupertinoAlertDialog 、CupertinoButton 和 CupertinoSwitch ,這些元件你可能看起來更熟悉。
Flutter為了減少開發者的負擔,建立了這個擁有Material和Cupertino風格的Widgets層。
二、綜合分析
思考一下,RenderObject 是如何與 Widgets 聯絡起來的呢? Flutter是如何建立佈局的?Element又是什麼呢?
上面已經對框架結構進行了簡單的介紹,接下來看一下真的的實踐,比如下面簡單的 控制元件樹。
ps: 我們實際開發中使用的 widget,比如 Text,都是由許多其他的 Widget 來組成的,為了演示和講解,這裡用抽象的 Widget (SimpleContainer 和 SimpleText) 來代替 。
我們構建的這個 APP 是非常簡單的。它由三個Stateless Widget組成:SimpleApp、SimpleContainer、SimpleText。當我們呼叫 Flutter 的 runApp() 方法會發生什麼呢?
當 Flutter 執行 runApp() 之後,這背後將會執行下面一些列事情:
1、Flutter 將會構建包含這三個 Widget 的 Widgets 樹。
2、Flutter 遍歷 Widget 樹,通過 Widget 裡面的 createElement 方法來建立其關聯的 Element 物件,然後將這些物件組建成 Element 樹。
3、最後 ,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 會不同的原因。
三、深入分析
因為 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 做了大量優化工作並且其使用的快取策略,能夠讓我們不必手動的對這些物件進行管理。
四、總結
現在你應該對 Flutter 是如何工作的,為什麼能夠渲染複雜的佈局並且執行的還很流暢有了一定的瞭解,這裡面還沒有講到 State,Flutter 引入了 State 來提升了這個過程的整體效能,今天暫時不對此進行討論了,希望這篇文章能夠幫助你理解 Flutter 框架。
推薦閱讀
歡迎關注「Flutter 程式設計開發」微信公眾號 。