10 分鐘瞭解 Flutter 跨平臺運行原理!

導語 | 本文將從選型、簡介和運行原理三大部分爲你介紹 Flutter 的相關概念,希望能站在框架設計和實現原理的高度,帶領大家去理解 Flutter 區別其他跨平臺解決方案的關鍵所在。

一、爲什麼選擇 Flutter

隨着無線時代的來臨,怎麼樣用最標準化的手段能夠讓更多的人開發這個頁面、怎麼樣能夠提供像 H5 一樣標準的頁面,成爲大前端時代開發者們最關心的事情。

我們把時間線拉長,來看看移動端跨平臺技術經過了一個怎樣的發展史:下面主要介紹在這個發展過程中跨平臺技術有了哪些進步或者做了哪些優化。

Ionic/Cordova(Hybrid): 在技術原理上的核心是,將原生的一些能力通過 JSBridge 封裝給 Web 來調用,擴充了 Web 應用能力。但是這種方法有兩個不足,一是依賴客戶端,二是在性能和體驗上都非常依賴於 Web 端。因此,整體上的體驗不可預知。目前這個技術還經常被應用到,例如,當前 App 內會提供白名單域名和可調用的 JSBridge 方法,由此來增強 H5 與客戶端交互能力,從而提升 App 內 H5 的靈活性。

React Native/Weex: 在原來的 Hybrid 的 JSBridge 基礎上進行改進,將 JavaScript 的界面以及交互轉化爲 Native 的控件,從而在體驗上和原生界面基本一致。但因爲是 JIT 模式,因此需要頻繁地在 JavaScript 與 Native 之間進行通信,從而會有一定的性能損耗影響,導致體驗上與原生會有一些差異。

Flutter: 取長補短,結合了之前的一些優點,解決了與 Native 之間通信的問題,同時也有了自渲染模式(框架自身實現了一套 UI 基礎框架,與原來的渲染模式基本一致)。從而在體驗和性能上相對之前的兩種框架表現都較好。

選擇 Flutter 並不是爲了代替 iOS 或者 Android,而是做一個技術互補,比如,Flutter 負責業務功能,而 iOS 和 Android 則負責部分的底層交互提供服務給到 Flutter 應用,這裏大膽預測一下未來跨端技術團隊的組成: 

二、Flutter 簡介

Flutter 是一款移動應用程序跨平臺框架,使用一種語言(Dart)編寫的同一份代碼可以生成 iOS 和 Android 兩個高性能、高保真的應用程序。

Flutter 目標是使開發人員能夠交付在不同平臺上都感覺自然流暢的高性能應用程序。兼容滾動行爲、排版、圖標等方面的差異。那麼 Flutter 是如何編譯成原生 app 的呢?

Flutter 不借助原生的渲染能力,而是自己實現了一套與 Android 和 iOS 一樣的渲染原理,從而在性能上與原生平臺保持基本一致。不過這裏由於目前 Flutter 只是一個 UI 框架,因此在原生功能方面還是需要依賴原生平臺,這也是它存在的一些問題。

三、Flutter 運行原理

如前面已提到的那樣,Flutter 是重寫了一整套包括底層渲染邏輯和上層開發語言的完整解決方案。這樣不僅可以保證視圖渲染在 Android 和 iOS 上的高度一致性(即高保真),在代碼執行效率和渲染性能上也可以媲美原生 App 的體驗(即高性能)。那 Flutter 是怎麼運行的呢?

我們從圖像顯示的基本原理說起。

在計算機系統中,圖像的顯示需要 CPU、GPU 和顯示器一起配合完成:CPU 負責圖像數據計算,GPU 負責圖像數據渲染,而顯示器則負責最終圖像顯示。

CPU 把計算好的、需要顯示的內容交給 GPU,由 GPU 完成渲染後放入幀緩衝區,隨後視頻控制器根據垂直同步信號(VSync)以每秒 60 次的速度,從幀緩衝區讀取幀數據交由顯示器完成圖像顯示。

操作系統在呈現圖像時遵循了這種機制,而 Flutter 作爲跨平臺開發框架也採用了這種底層方案。下面有一張更爲詳盡的示意圖來解釋 Flutter 的繪製原理。

可以看到,Flutter 關注如何儘可能快地在兩個硬件時鐘的 VSync 信號之間計算併合成視圖數據,然後通過 Skia 交給 GPU 渲染:UI 線程使用 Dart 來構建視圖結構數據,這些數據會在 GPU 線程進行圖層合成,隨後交給 Skia 引擎加工成 GPU 數據,而這些數據會通過 OpenGL 最終提供給 GPU 渲染。

在進一步學習 Flutter 之前,我們有必要了解下構建 Flutter 的關鍵技術,即 Skia 和 Dart。

備註

  1. Skia 是一款用 C++ 開發的、性能彪悍的 2D 圖像繪製引擎,Skia 保證了同一套代碼調用在 Android 和 iOS 平臺上的渲染效果是完全一致的。

  2. Dart 是一個專注於前端(mobile/web)UI(用戶交互)開發的強類型語言。

在瞭解了 Flutter 的基本運作機制後,我們再來深入瞭解一下 Flutter 的實現原理。

首先,我們來看一下 Flutter 的架構圖。我希望通過這張圖以及對應的解讀,你能在開始學習的時候就建立起對 Flutter 的整體印象。

注:此圖引自 Flutter System 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 採用深度優先機制遍歷渲染對象樹,決定渲染對象樹中各渲染對象在屏幕上的位置和尺寸。在佈局過程中,渲染對象樹中的每個渲染對象都會接收父對象的佈局約束參數,決定自己的大小,然後父對象按照控件邏輯決定各個子對象的位置,完成佈局過程。

爲了防止因子節點發生變化而導致整個控件樹重新佈局,Flutter 加入了一個機制——佈局邊界(Relayout Boundary),可以在某些節點自動或手動地設置佈局邊界,當邊界內的任何對象發生重新佈局時,不會影響邊界外的對象,反之亦然。

(二)繪製

佈局完成後,渲染對象樹中的每個節點都有了明確的尺寸和位置。Flutter 會把所有的渲染對象繪製到不同的圖層上。與佈局過程一樣,繪製過程也是深度優先遍歷,而且總是先繪製自身,再繪製子節點。

以下圖爲例:節點 1 在繪製完自身後,會再繪製節點 2,然後繪製它的子節點 3、4 和 5,最後繪製節點 6。

可以看到,由於一些其他原因(比如,視圖手動合併)導致 2 的子節點 5 與它的兄弟節點 6 處於了同一層,這樣會導致當節點 2 需要重繪的時候,與其無關的節點 6 也會被重繪,帶來性能損耗。

爲了解決這一問題,Flutter 提出了與佈局邊界對應的機制——重繪邊界(Repaint Boundary)。在重繪邊界內,Flutter 會強制切換新的圖層,這樣就可以避免邊界內外的互相影響,避免無關內容置於同一圖層引起不必要的重繪。

重繪邊界的一個典型場景是 Scrollview。ScrollView 滾動的時候需要刷新視圖內容,從而觸發內容重繪。而當滾動內容重繪時,一般情況下其他內容是不需要重繪的,這時候重繪邊界就派上用場了。

(三)合成和渲染

終端設備的頁面越來越複雜,因此 Flutter 的渲染樹層級通常很多,直接交付給渲染引擎進行多圖層渲染,可能會出現大量渲染內容的重複繪製,所以還需要先進行一次圖層合成,即將所有的圖層根據大小、層級、透明度等規則計算出最終的顯示效果,將相同的圖層歸類合併,簡化渲染樹,提高渲染效率。

合併完成後,Flutter 會將幾何圖層數據交由 Skia 引擎加工成二維圖像數據,最終交由 GPU 進行渲染,完成界面的展示。

四、總結

咱們從各種業界主流跨端方案與 Flutter 的對比開始,到 Flutter 的簡要介紹以及 Flutter 的運行機制,並以界面渲染過程爲例,從佈局、繪製、合成和渲染三個階段講述了 Flutter 的實現原理。相信大家對 Flutter 已經有一個整體認知,趕快一起上手操作起來吧!

** 作者簡介**

賓俊文

騰訊 IEG 直播業務部 前端工程師

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/hVRS47YOAcccG9Xaj8oAdA