Flutter 是什么?它出現的動機是什么,解決了哪些痛點?相比其他跨平臺技術,Flutter 的優勢在哪里?……相信很多人在第一眼看到 Flutter 時,都會有類似的疑問。
別急,在今天的這篇文章中,我會與你介紹 Flutter 的歷史背景和運行機制,并以界面渲染過程為例與你講述其實現原理,讓你對 Flutter 能夠有一個全方位的認知和感受。在對 Flutter 有了全面了解后,這些疑問自然也就迎刃而解了。
接下來,我們就從 Flutter 出現的歷史背景開始談起吧。
## Flutter 出現的歷史背景
為不同的操作系統開發擁有相同功能的應用程序,開發人員只有兩個選擇:
1. 使用原生開發語言(即 Java 和 Objective-C),針對不同平臺分別進行開發。
2. 使用跨平臺解決方案,對不同平臺進行統一開發。
原生開發方式的體驗最好,但研發效率和研發成本相對較高;而跨平臺開發方式研發雖然效率高,但為了抹平多端平臺差異,各類解決方案暴露的組件和 API 較原生開發相比少很多,因此研發體驗和產品功能并不完美。
所以,最成功的跨平臺開發方案其實是依托于瀏覽器控件的 Web。瀏覽器保證了 99% 的概率下 Web 的需求都是可以實現的,不需要業務將就“技術”。不過,Web 最大的問題在于它的性能和體驗與原生開發存在肉眼可感知的差異,因此并不適用于對體驗要求較高的場景。
對于用戶體驗更接近于原生的 React Native,對業務的支持能力卻還不到瀏覽器的 5%,僅適用于中低復雜度的低交互類頁面。面對稍微復雜一點兒的交互和動畫需求,開發者都需要 case by case 地去 review,甚至還可能要通過原生代碼去擴展才能實現。
這些因素,也就導致了雖然跨平臺開發從移動端誕生之初就已經被多次提及,但到現在也沒有被很好地解決。
帶著這些問題,我們終于迎來了本次專欄的主角——Flutter。
Flutter 是構建 Google 物聯網操作系統 Fuchsia 的 SDK,主打跨平臺、高保真、高性能。開發者可以通過 Dart 語言開發 App,一套代碼可以同時運行在 iOS 和 Android 平臺。 Flutter 使用 Native 引擎渲染視圖,并提供了豐富的組件和接口,這無疑為開發者和用戶都提供了良好的體驗。
從 2017 年 5 月,谷歌公司發布的了 Alpha 版本的 Flutter,到 2018 年底 Flutter Live 發布的 1.0 版本,再到現在最新的 1.5 版本(截止至 2019 年 7 月 1 日),Flutter 正在贏得越來越多的關注。
很多人開始感慨,跨平臺技術似乎終于迎來了最佳解決方案。那么,接下來我們就從原理層面去看看,Flutter 是如何解決既有跨平臺開發方案問題的。
## Flutter 是怎么運轉的?
與用于構建移動應用程序的其他大多數框架不同,Flutter 是重寫了一整套包括底層渲染邏輯和上層開發語言的完整解決方案。這樣不僅可以保證視圖渲染在 Android 和 iOS 上的高度一致性(即高保真),在代碼執行效率和渲染性能上也可以媲美原生 App 的體驗(即高性能)。
這,就是 Flutter 和其他跨平臺方案的本質區別:
* React Native 之類的框架,只是通過 JavaScript 虛擬機擴展調用系統組件,由 Android 和 iOS 系統進行組件的渲染;
* Flutter 則是自己完成了組件渲染的閉環。
那么,**Flutter 是怎么完成組件渲染的呢**?這需要從圖像顯示的基本原理說起。
在計算機系統中,圖像的顯示需要 CPU、GPU 和顯示器一起配合完成:CPU 負責圖像數據計算,GPU 負責圖像數據渲染,而顯示器則負責最終圖像顯示。
CPU 把計算好的、需要顯示的內容交給 GPU,由 GPU 完成渲染后放入幀緩沖區,隨后視頻控制器根據垂直同步信號(VSync)以每秒 60 次的速度,從幀緩沖區讀取幀數據交由顯示器完成圖像顯示。
操作系統在呈現圖像時遵循了這種機制,而 Flutter 作為跨平臺開發框架也采用了這種底層方案。下面有一張更為詳盡的示意圖來解釋 Flutter 的繪制原理。
:-: 
圖 1 Flutter 繪制原理
可以看到,Flutter 關注如何盡可能快地在兩個硬件時鐘的 VSync 信號之間計算并合成視圖數據,然后通過 Skia 交給 GPU 渲染:UI 線程使用 Dart 來構建視圖結構數據,這些數據會在 GPU 線程進行圖層合成,隨后交給 Skia 引擎加工成 GPU 數據,而這些數據會通過 OpenGL 最終提供給 GPU 渲染。
在進一步學習 Flutter 之前,我們有必要了解下構建 Flutter 的關鍵技術,即 Skia 和 Dart。
## Skia 是什么?
要想了解 Flutter,你必須先了解它的底層圖像渲染引擎 Skia。因為,Flutter 只關心如何向 GPU 提供視圖數據,而 Skia 就是它向 GPU 提供視圖數據的好幫手。
Skia 是一款用 C++ 開發的、性能彪悍的 2D 圖像繪制引擎,其前身是一個向量繪圖軟件。2005 年被 Google 公司收購后,因為其出色的繪制表現被廣泛應用在 Chrome 和 Android 等核心產品上。Skia 在圖形轉換、文字渲染、位圖渲染方面都表現卓越,并提供了開發者友好的 API。
目前,Skia 已然是 Android 官方的圖像渲染引擎了,因此 Flutter Android SDK 無需內嵌 Skia 引擎就可以獲得天然的 Skia 支持;而對于 iOS 平臺來說,由于 Skia 是跨平臺的,因此它作為 Flutter iOS 渲染引擎被嵌入到 Flutter 的 iOS SDK 中,替代了 iOS 閉源的 Core Graphics/Core Animation/Core Text,這也正是 Flutter iOS SDK 打包的 App 包體積比 Android 要大一些的原因。
底層渲染能力統一了,上層開發接口和功能體驗也就隨即統一了,開發者再也不用操心平臺相關的渲染特性了。也就是說,Skia 保證了同一套代碼調用在 Android 和 iOS 平臺上的渲染效果是完全一致的。
## 為什么是 Dart?
除了我們在第 2 篇預習文章“[預習篇 · Dart 語言概覽](https://time.geekbang.org/column/article/104071)”中提到的,Dart 因為同時支持 AOT 和 JIT,所以具有運行速度快、執行性能好的特點外,Flutter 為什么選擇了 Dart,而不是前端應用的準官方語言 JavaScript 呢?這個問題很有意思,但也很有爭議。
很多人說,選擇 Dart 是 Flutter 推廣的一大劣勢,畢竟多學一門新語言就多一層障礙。想想 Java 對 Android,JavaScript 對 NodeJS 的推動,如果換個語言可能就不一樣了。
但,**Google 公司給出的原因很簡單也很直接**:Dart 語言開發組就在隔壁,對于 Flutter 需要的一些語言新特性,能夠快速在語法層面落地實現;而如果選擇了 JavaScript,就必須經過各種委員會和瀏覽器提供商漫長的決議。
事實上,Flutter 的確得到了兄弟團隊的緊密支持。2018 年 2 月發布的 Dart 2.0,2018 年 12 月發布的 Dart 2.1,2019 年 2 月發布的 Dart 2.2,2019 年 5 月發布的 Dart2.3,每次發布都包含了為 Flutter 量身定制的諸多改造(比如,改進的 AOT 性能、更智能的類型隱式轉換等)。
當然,Google 公司選擇使用 Dart 作為 Flutter 的開發語言,我想還有其他更有說服力的理由:
1. Dart 同時支持即時編譯 JIT 和事前編譯 AOT。在開發期使用 JIT,開發周期異常短,調試方式顛覆常規(支持有狀態的熱重載);而發布期使用 AOT,本地代碼的執行更高效,代碼性能和用戶體驗也更卓越。
2. Dart 作為一門現代化語言,集百家之長,擁有其他優秀編程語言的諸多特性(比如,完善的包管理機制)。也正是這個原因,Dart 的學習成本并不高,很容易上手。
3. Dart 避免了搶占式調度和共享內存,可以在沒有鎖的情況下進行對象分配和垃圾回收,在性能方面表現相當不錯。
Dart 是一門優秀的現代語言,最初設計也是為了取代 JavaScript 成為 Web 開發的官方語言。競爭對手如此強勁,最后的結果可想而知。這,也是為什么相比于其他熱門語言,Dart 的生態要冷清不少的原因。
而隨著 Flutter 的發布,Dart 開始轉型,其自身定位也發生了變化,專注于改善構建客戶端應用程序的體驗,因此越來越多的開發者開始慢慢了解、學習這門語言,并共同完善它的生態。憑借著 Flutter 的火熱勢頭,輔以 Google 強大的商業運作能力,相信轉型后的 Dart 前景會非常光明。
## Flutter 的原理
在了解了 Flutter 的基本運作機制后,我們再來深入了解一下 Flutter 的實現原理。
首先,我們來看一下 Flutter 的架構圖。我希望通過這張圖以及對應的解讀,你能在開始學習的時候就建立起對 Flutter 的整體印象,能夠從框架設計和實現原理的高度去理解 Flutter 區別其他跨平臺解決方案的關鍵所在,為后面的學習打好基礎,而不是直接一上來就陷入語言和框架的功能細節“泥潭”而無法自拔。
:-: 
圖 2 Flutter 架構圖
備注:此圖引自[Flutter System Overview](https://flutter.dev/docs/resources/technical-overview)
Flutter 架構采用分層設計,從下到上分為三層,依次為:Embedder、Engine、Framework。
* Embedder 是操作系統適配層,實現了渲染 Surface 設置,線程設置,以及平臺插件等平臺相關特性的適配。從這里我們可以看到,Flutter 平臺相關特性并不多,這就使得從框架層面保持跨端一致性的成本相對較低。
* Engine 層主要包含 Skia、Dart 和 Text,實現了 Flutter 的渲染引擎、文字排版、事件處理和 Dart 運行時等功能。Skia 和 Text 為上層接口提供了調用底層渲染和排版的能力,Dart 則為 Flutter 提供了運行時調用 Dart 和渲染引擎的能力。而 Engine 層的作用,則是將它們組合起來,從它們生成的數據中實現視圖渲染。
* Framework 層則是一個用 Dart 實現的 UI SDK,包含了動畫、圖形繪制和手勢識別等功能。為了在繪制控件等固定樣式的圖形時提供更直觀、更方便的接口,Flutter 還基于這些基礎能力,根據 Material 和 Cupertino 兩種視覺設計風格封裝了一套 UI 組件庫。我們在開發 Flutter 的時候,可以直接使用這些組件庫。
接下來,我**以界面渲染過程為例,和你介紹 Flutter 是如何工作的。**
頁面中的各界面元素(Widget)以樹的形式組織,即控件樹。Flutter 通過控件樹中的每個控件創建不同類型的渲染對象,組成渲染對象樹。而渲染對象樹在 Flutter 的展示過程分為四個階段:布局、繪制、合成和渲染。
### 布局
Flutter 采用深度優先機制遍歷渲染對象樹,決定渲染對象樹中各渲染對象在屏幕上的位置和尺寸。在布局過程中,渲染對象樹中的每個渲染對象都會接收父對象的布局約束參數,決定自己的大小,然后父對象按照控件邏輯決定各個子對象的位置,完成布局過程。
:-: 
圖 3 Flutter 布局過程
為了防止因子節點發生變化而導致整個控件樹重新布局,Flutter 加入了一個機制——布局邊界(Relayout Boundary),可以在某些節點自動或手動地設置布局邊界,當邊界內的任何對象發生重新布局時,不會影響邊界外的對象,反之亦然。
:-: 
圖 4 Flutter 布局邊界
### 繪制
布局完成后,渲染對象樹中的每個節點都有了明確的尺寸和位置。Flutter 會把所有的渲染對象繪制到不同的圖層上。與布局過程一樣,繪制過程也是深度優先遍歷,而且總是先繪制自身,再繪制子節點。
以下圖為例:節點 1 在繪制完自身后,會再繪制節點 2,然后繪制它的子節點 3、4 和 5,最后繪制節點 6。
:-: 
圖 5 Flutter 繪制示例
可以看到,由于一些其他原因(比如,視圖手動合并)導致 2 的子節點 5 與它的兄弟節點 6 處于了同一層,這樣會導致當節點 2 需要重繪的時候,與其無關的節點 6 也會被重繪,帶來性能損耗。
為了解決這一問題,Flutter 提出了與布局邊界對應的機制——重繪邊界(Repaint Boundary)。在重繪邊界內,Flutter 會強制切換新的圖層,這樣就可以避免邊界內外的互相影響,避免無關內容置于同一圖層引起不必要的重繪。
:-: 
圖 6 Flutter 重繪邊界
重繪邊界的一個典型場景是 Scrollview。ScrollView 滾動的時候需要刷新視圖內容,從而觸發內容重繪。而當滾動內容重繪時,一般情況下其他內容是不需要重繪的,這時候重繪邊界就派上用場了。
### 合成和渲染
終端設備的頁面越來越復雜,因此 Flutter 的渲染樹層級通常很多,直接交付給渲染引擎進行多圖層渲染,可能會出現大量渲染內容的重復繪制,所以還需要先進行一次圖層合成,即將所有的圖層根據大小、層級、透明度等規則計算出最終的顯示效果,將相同的圖層歸類合并,簡化渲染樹,提高渲染效率。
合并完成后,Flutter 會將幾何圖層數據交由 Skia 引擎加工成二維圖像數據,最終交由 GPU 進行渲染,完成界面的展示。這部分內容,我已經在前面的內容中介紹過,這里就不再贅述了。
接下來,我們再看看學習 Flutter,都需要學習哪些知識。
## 學習 Flutter 需要掌握哪些知識?
終端設備越來越碎片化,需要支持的操作系統越來越多,從研發效率和維護成本綜合考慮,跨平臺開發一定是未來大前端的趨勢,我們應該擁抱變化。而 Flutter 提供了一套徹底的移動跨平臺方案,也確實彌補了如今跨平臺開發框架的短板,解決了業界痛點,極有可能成為跨平臺開發領域的終極解決方案,前途非常光明。
那么,**我們學習 Flutter 都需要掌握哪些知識呢?**
我按照 App 的開發流程(開發、調試測試、發布與線上運維)將 Flutter 的技術棧進行了劃分,里面幾乎包含了 Flutter 開發需要的所有知識點。而這些所有知識點,我會在專欄中為你一一講解。掌握了這些知識點后,你也就具備了企業級應用開發的必要技能。
這些知識點,如下圖所示:
:-: 
圖 7 Flutter 知識體系
有了這張圖,你是否感覺到學習 Flutter 的路線變得更加清晰了呢?
## 小結
今天,我帶你了解了 Flutter 的歷史背景與運行機制,并以界面渲染過程為例,從布局、繪制、合成和渲染四個階段講述了 Flutter 的實現原理。此外,我向你介紹了構建 Flutter 底層的關鍵技術:Skia 與 Dart,它們是 Flutter 有別于其他跨平臺開發方案的核心所在。
最后,我梳理了一張 Flutter 學習思維導圖,圍繞一個應用的迭代周期介紹了 Flutter 相關的知識點。我希望通過這個專欄,能和你把 Flutter 背后的設計原理和知識體系講清楚,讓你能對 Flutter 有一個整體感知。這樣,在你學完這個專欄以后,就能夠具備企業級應用開發的理論基礎與實踐。
- 前言
- 開篇詞
- 預習篇
- 01丨預習篇 · 從0開始搭建Flutter工程環境
- 02丨預習篇 · Dart語言概覽
- Flutter開發起步
- 03丨深入理解跨平臺方案的歷史發展邏輯
- 04丨Flutter區別于其他方案的關鍵技術是什么?
- 05丨從標準模板入手,體會Flutter代碼是如何運行在原生系統上的
- Dart語言基礎
- 06丨基礎語法與類型變量:Dart是如何表示信息的?
- 07丨函數、類與運算符:Dart是如何處理信息的?
- 08丨綜合案例:掌握Dart核心特性
- Flutter基礎
- 09丨Widget,構建Flutter界面的基石
- 10丨Widget中的State到底是什么?
- 11丨提到生命周期,我們是在說什么?
- 12丨經典控件(一):文本、圖片和按鈕在Flutter中怎么用?
- 13丨ListView在Flutter中是什么?
- 14 丨 經典布局:如何定義子控件在父容器中排版位置?
- 15 丨 組合與自繪,我該選用何種方式自定義Widget?
- 16 丨 從夜間模式說起,如何定制不同風格的App主題?
- 17丨依賴管理(一):圖片、配置和字體在Flutter中怎么用?
- 18丨依賴管理(二):第三方組件庫在Flutter中要如何管理?
- 19丨用戶交互事件該如何響應?
- 20丨關于跨組件傳遞數據,你只需要記住這三招
- 21丨路由與導航,Flutter是這樣實現頁面切換的
- Flutter進階
- 22丨如何構造炫酷的動畫效果?
- 23丨單線程模型怎么保證UI運行流暢?
- 24丨HTTP網絡編程與JSON解析
- 25丨本地存儲與數據庫的使用和優化
- 26丨如何在Dart層兼容Android-iOS平臺特定實現?(一)
- 27丨如何在Dart層兼容Android-iOS平臺特定實現?(二)
- 28丨如何在原生應用中混編Flutter工程?
- 29丨混合開發,該用何種方案管理導航棧?
- 30丨為什么需要做狀態管理,怎么做?
- 31丨如何實現原生推送能力?
- 32丨適配國際化,除了多語言我們還需要注意什么
- 33丨如何適配不同分辨率的手機屏幕?
- 34丨如何理解Flutter的編譯模式?
- 35丨HotReload是怎么做到的?
- 36丨如何通過工具鏈優化開發調試效率?
- 37丨如何檢測并優化FlutterApp的整體性能表現?
- 38丨如何通過自動化測試提高交付質量?
- Flutter綜合應用
- 39丨線上出現問題,該如何做好異常捕獲與信息采集?
- 40丨衡量FlutterApp線上質量,我們需要關注這三個指標
- 41丨組件化和平臺化,該如何組織合理穩定的Flutter工程結構?
- 42丨如何構建高效的FlutterApp打包發布環境?
- 43丨如何構建自己的Flutter混合開發框架(一)?
- 44丨如何構建自己的Flutter混合開發框架(二)?
- 結束語
- 結束語丨勿畏難,勿輕略