Flutter渲染之Widget、Element、RenderObject

1、Flutter架構

衆所周知,Flutter是由Google推出的開源的高性能跨平臺框架,一個2D渲染引擎。在Flutter中,Widget是Flutter用戶界面的基本構成單元,能夠說一切皆Widget。與Weex和RN框架使用的JsCore轉化的中間層不一樣,Flutter採用的是全新的架構方案,擁有本身的渲染引擎和Dart上層,每一層都創建在前一層的基礎之上,而且上層比下層的使用頻率更高,其框架架構以下圖所示。
在這裏插入圖片描述
能夠看到,自下而上,Flutter分爲Embedder、Engine和Framework三層。其中,Embedder是操做系統適配層,主要負責Surface渲染設置,線程設置,以及平臺插件等平臺相關特性的適配;Engine層負責圖形繪製、文字排版和提供Dart運行時,Engine層具備獨立虛擬機,正是因爲它的存在,Flutter程序才能運行在不一樣的平臺上,實現跨平臺運行;Framework層則是使用Dart編寫的一套基礎視圖庫,包含了動畫、圖形繪製和手勢識別等功能,是使用頻率最高的一層。架構

  • Flutter Embedder:Embedder是Flutter的操做系統適配層,又稱爲嵌入層,經過該層能夠把Flutter嵌入到各個不一樣的平臺上去。Embedder的主要工做包括Surface渲染設置、線程設置、事件循環以及插件的平臺適配等。
  • Flutter Engine:純 C++實現的 SDK,其中包括 Skia引擎、Dart運行時、文字排版引擎等。它是 Dart的一個運行時,它能夠以 JIT 或者 AOT的模式運行 Dart代碼。這個運行時還控制着 VSync信號的傳遞、GPU數據的填充等,而且還負責把客戶端的事件傳遞到運行時中的代碼。
  • Flutter Framework:純 Dart實現的 SDK,提供了一整套自底向上的基礎庫, 用於處理動畫、繪圖和手勢。而且基於繪圖封裝了一套 UI組件庫,而後根據 Material 和Cupertino兩種視覺風格區分開來。在平時應用開發中,與開發者打交道最多的就是這一層,而且最多的就是各類Widget。

2、渲染流程

不論是什麼渲染框架,其基本的原理都是:通常以60Hz的固定頻率刷新,每一幀圖像繪製完成後,會繼續繪製下一幀,而後顯示器就會發出一個Vsync信號,按60Hz計算,屏幕每秒會發出60次這樣的信號。CPU計算好顯示內容提交給GPU,GPU渲染好交給顯示器顯示。框架

在Flutter中,渲染會用到不少的線程,主要是UI線程和GPU線程,下圖是Flutter App線程的運做原理圖。
在這裏插入圖片描述
下面重點看一下UI線程和GPU線程。異步

UI Task Runner

UI Task Runner用於執行Root Isolate代碼,它運行在線程對應平臺的線程上,屬於子線程。同時,Root isolate在引擎啓動時會綁定很多Flutter須要的函數方法,這些綁定的函數能夠提交渲染幀給Engine層執行渲染操做,下圖演示了Widgets生成Layer Tree的過程。
在這裏插入圖片描述
對於每一幀,引擎經過Root Isolate通知Flutter Engine有幀須要渲染,平臺收到Flutter Engine通知後會建立對象和組件並生成一個Layer Tree,而後將生成的Layer Tree提交給Flutter Engine。此時,只生成了須要繪製的內容,並無執行屏幕渲染,而Root Isolate就是負責將建立的Layer Tree繪製到屏幕上,所以若是線程過載會致使卡頓掉幀現象。函數

除了用於處理渲染以外,Root Isolate還須要處理來自Native Plugins的消息響應、Timers、MicroTasks和異步IO。若是確實有沒法避免的繁重計算,建議將這些耗時的操做放到獨立的Isolate去執行,從而避免應用UI卡頓問題。佈局

GPU Task Runner

GPU Task Runner用於執行設備GPU指令,UI Task Runner建立的Layer Tree是跨平臺的。也就是說,Layer Tree提供了繪製所須要的信息,可是由誰來完成繪製它是不關心的。性能

GPU Task Runner的主要責任就是負責將Layer Tree提供的信息轉化爲平臺可執行的GPU指令,同時它也負責管理每一幀繪製所須要的GPU資源,包括平臺Framebuffer的建立,Surface生命週期管理,以及Texture和Buffers的繪製時機等,下圖GPU Task Runner的工做流程。動畫

在這裏插入圖片描述
UI Runner和GPU Runner運行在不一樣的線程。GPU Runner會根據目前幀執行的進度去向UI Runner請求下一幀的數據,在任務繁重的時候還可能會出現UI Runner的延遲任務。不過這種調度機制的好處在於,確保GPU Runner不至於過載,同時也避免了UI Runner沒必要要的資源消耗。ui

GPU Runner能夠致使UI Runner的幀調度的延遲,GPU Runner的過載會致使Flutter應用的卡頓,所以在實際使用過程當中,建議爲每個Engine實例都新建一個專用的GPU Runner線程。this

3、Widget、Element 和 RenderObject

要理解Flutter的渲染原理,那麼就必須瞭解Widget、RenderObject 和 Element及其做用。總的來講,Flutter調用runApp(rootWidget),將rootWidget傳給rootElement,作爲rootElement的子節點,生成Element樹,由Element樹生成Render樹,以下圖所示。
在這裏插入圖片描述spa

從上面的介紹中,咱們隱約知道了Widget、RenderObject 和 Element的做用,簡單的介紹一下。

  • Widget:Widget 的主要做用是用來保存 Element 信息的(包括佈局、渲染屬性、事件響應等信息),自己是不可變的,Element 也是根據 Widget 裏面保存的配置信息來管理渲染樹,以及決定自身是否須要執行渲染。
  • RenderObject:RenderObject 作爲渲染樹中的對象存在,主要做用是處理佈局、繪製相關的事情,而繪製的內容是Widget傳入的內容。
  • Element:Element 能夠理解爲是其關聯的 Widget 的實例,存放視圖構建的上下文數據,能夠經過遍歷Element來查看視圖樹,Element同時持有Widget和RenderObject對象。

Flutter經過Widget樹中的每一個控件建立不一樣類型的渲染對象,組成渲染對象樹,而渲染對象樹在Flutter中的展現分爲四階段:佈局、繪製、合成及渲染。其中,佈局和繪製由RenderObject負責完成,Flutter採用深度優先機制遍歷渲染樹對象,肯定樹中每一個對象的位置和尺寸,並把他們繪製到不一樣的圖層上,而合成及渲染則交給Skia完成。

下圖展現了Widget、Element 和 RenderObject的關係。
在這裏插入圖片描述

3.1 Widget

在 Flutter 中,萬物皆是 Widget,不管是可見的仍是功能型的,下面是官方對Widget的介紹。
在這裏插入圖片描述

  • Widget 的做用是用來保存 Element 的配置信息的。
  • Widget 自己是不可變的。
  • Element 根據 Widget 裏面保存的配置信息來管理渲染樹。
  • Widget 能夠屢次的插入到 Widget 樹中,每插入一次,Element 都要從新裝載一遍 Widget 。
  • Widget 裏面的 key 屬性用來決定依賴這個 Widget 的 Element 在 Element 樹中是更新仍是移除。

下面是Widget源碼。

abstract class Widget extends DiagnosticableTree{
  const Widget({ this.key });
  final Key key;
  
  @protected
  Element createElement();
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
   return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget有兩個重要的方法,一個是經過 createElement 來建立 Element 對象的,一個是根據 key 來決定更新行爲的 canUpdate 方法。

3.2 RenderObject

RenderObject class

  • RenderObject 是作爲渲染樹中的對象存在。
  • RenderObject 不定義約束關係,也就是不會對子控件的佈局位置、大小等進行管理。
  • RenderObject 中有一個 parentData 屬性,這個屬性用來保存其孩子節點的特定信息,如子節點位置,這個屬性對其孩子是透明的。

RenderObject的源碼以下。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ParentData parentData;
  Constraints _constraints;
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    
  }
  void paint(PaintingContext context, Offset offset) { }
  
  void performLayout();
  void markNeedsPaint() {
  }
}

能夠看出,RenderObject 的主要做用就是繪製和佈局。RenderObject 在 Flutter 中的做用分爲四個階段,即佈局、繪製、合成和渲染。其中,佈局和繪製在 RenderObject 中完成,Flutter 採用深度優先機制遍歷渲染對象樹,肯定樹中各個對象的位置和尺寸,並把它們繪製在不一樣的圖層上。繪製完畢後,合成和渲染的工做則交給 Skia 完成。

3.3 Element

Element class

  • Element 是關聯的Widget 的實例,而且關聯在 Widget 樹的特定位置上。
  • Widget 是不可變的,一個 Widget 能夠同時用來配置多個子 Widget 樹,而 Element 就用來表明特定位置的 Widget 。
  • Widget 是不可變的,而 Element 是可變的,Element決定是否須要刷新界面。
  • 一些 Element 只能有一個子節點,如 Container、Opacity、Center ,還有一些能夠有多個子節點,如 Column、Row 和 ListView 等。

Element 擁有本身的生命週期:

  • Flutter framework 經過 Widget.createElement 來建立一個 Element 。
  • 每當 Widget 建立並插入到 Widget 樹中時,framework 就會經過 mount 方法來把這個 widget 建立並關聯的 Element 插入到 Element 樹中。
  • 經過 attachRenderObject 方法來將 render objects 來關聯到 Render 樹上,這時能夠認爲 Widget 已經顯示在屏幕上了。
  • 每當執行了 rebuid 方法,Widget 表明的配置信息改變時,framewrok 就會調用這個新的 Widget 的 update 方法執行重繪。
  • 當 Element 的祖先想要移除一個子 Element 時,能夠經過 deactivateChild 方法,先把這個 Element 從 樹中移除,而後將這個 Element 加入到一個「不活躍元素列表」中,接着 framework 就會將這個 element 從屏幕移除。

總的來講,Flutter提出一切皆Widget,Widget 主要用來保存 Element 信息,而Element做用Widget 的實例,存放視圖構建的上下文數據,而且同時持有Widget和RenderObject對象,RenderObject的主要做用是處理佈局、繪製相關的事情,肯定Element樹中每一個對象的位置和尺寸。

相關文章
相關標籤/搜索