Flutter渲染 Widget Element RenderObject概述(一)

Logos發表於2019-12-25

注意:為了讓分析更加簡單,和邏輯清晰,我們去掉了部分原始碼和註釋,只留下了主要的程式碼和邏輯。

最近一直在研究Flutter的渲染問題,在深入探索之後發現總是繞不過三個物件分別是Widget,Element,RenderObject,那麼Flutter為什麼需要這三個物件,這個三個物件是什麼關係?有這三個物件會提高渲染效率嗎?等等這樣的問題,我將在接下來的幾篇文章中為大家找答案。

Widget概述

widget定義

先給出Widget的定義,可能和你之前理解的元件有一點區別,下面是Flutter對Widget的定義。

Describes the configuration for an [Element].

翻譯過來的大概意思就是,"對一個Element配置的描述"。

這個概念上透露了兩點細節,第一是,Widget是Element的配置描述,有人一定會問,Element是什麼呢?在下面的章節中我們將詳細介紹Element。第二是,Widget只是一個配置描述,不是真正的渲染物件,這裡可能有點繞。舉個例子,看大家能不能理解,Widget就好比是Android開發中的xml,只是描述了一些View的顏色,大小等,真正在螢幕上顯示的是View。

在Flutter中,一切都是元件。在移動端開發中元件的概念很常見,比如Android的四大元件,Flutter把元件的概念發揮到了極致,在Flutter中,手勢(GestureDetector)都是元件,下面是GestureDetector的原始碼。

class GestureDetector extends StatelessWidget {
}

abstract class StatelessWidget extends Widget {
}
複製程式碼

GestureDetector繼承自StatelessWidget,StatelessWidget是沒有狀態的元件,這個類繼承Widget,可以看出來GestureDetector也是Widget,接下來我們簡單分析一下Widget,下面是Widget的原始碼。

abstract class Widget {
  const Widget({ this.key });
  final Key key;
  
  @protected
  Element createElement();//註釋1
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {//註釋2
   return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製程式碼

原來Widget是一個抽象類,這個類有一個建構函式,引數是一個key,這個key有一個很重要的功能,用途是比較兩個Widget是不是同一個Widget,在註釋2就用到了這個key。然後有兩個方法,分別是createElement()和一個靜態的方法canUpdate()。

  • 註釋1

createElement()是一個抽象方法,子類必須實現這個方法,但是大部分我們用都是系統的Widget,比如StatefulWidget和StatelessWidget,他們都預設實現了這方法,這方法也非常簡單,建立了一個Element。這裡面隱含了一很重要問題的答案,開篇我們問了這樣的問題,這個三個物件是什麼關係?現在我們至少知道了Widget和Element的關係了,一個Widget有一個Element物件,是通過createElement()建立的

  • 註釋2

canUpdate()方法很簡單,就是判斷oldWidget和newWidget是不是同一個Widget,如果他們的runtimeType和key相同,就認為是同一個Widget。

Widget的特性

Widget是一個很重要的概念,但是Widget有一個更重重要的特性,就是Widget是immutable(不可變的)的,這是什麼意思?下面我們講解一下,我們拿Opacity為例給大家講解,講解之前我們先看一下Opacity的繼承關係。(在講原始碼之前我們先看一下Opacity的職責是什麼,Opacity是一個能讓他的孩子透明的元件,很簡單也很容易理解。)

class Opacity extends SingleChildRenderObjectWidget {
}

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
}

abstract class RenderObjectWidget extends Widget {
}
複製程式碼

Flutter渲染 Widget Element RenderObject概述(一)

從上面可以看出來,Opacity繼承自SingleChildRenderObjectWidget,這類只包含了一個child的Widget,它繼承自RenderObjectWidget,RenderObjectWidget繼承自Widget。下面是具體分析一下Opacity,下面是是原始碼。

class Opacity extends SingleChildRenderObjectWidget {
  
  const Opacity({
    Key key,
    @required this.opacity,
    Widget child,
  }) : super(key: key, child: child);
  
  final double opacity;//註釋1
  
  @override
  RenderOpacity createRenderObject(BuildContext context) {//註釋2
    return RenderOpacity(
      opacity: opacity
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
    renderObject
      ..opacity = opacity
  }
}
複製程式碼
  • 註釋1

在註釋1處宣告瞭一個屬性,這屬性是final,也就除了建構函式能給這個屬性賦值之外,沒有其他的辦法讓這個值進行改變。那我們想改變這個值怎麼辦,唯一的辦法就是建立一個新的Opacity。

為什麼這樣設計呢?先透露一下這是Flutter的核心設計哲學,在接下來的章節中我們將詳細為大家講解。

總結

Widget好像是Android得一個xml配置檔案,不參與真正的渲染,只是告訴渲染層我長什麼樣式,並且這個物件的屬性是不可以改變的,要想改變只能重現建立一個物件。

Element概述

Element定義

還是老規矩,先看一下定義。

An instantiation of a [Widget] at a particular location in the tree.

翻譯過來的大概意思就是,"在Element表示一個Widget樹中特定位置的例項",下面我們看一下Element類的原始碼。

abstract class Element extends DiagnosticableTree implements BuildContext {
   Element(Widget widget)
    : _widget = widget;//註釋1
  
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {//註釋2
  }
  
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  }
}
複製程式碼

上面找了2個重點的方法和1個重要的屬性,其實Element的屬性和方法非常多,通過建構函式可以看出來,一個Element持有一個Widget,下面我們分析一下Element的建立過程。

Element建立

通過上面的Widget概述那一節我們知道,Widget有一個抽象方法createElement(),用來建立Element的,這個方法的具體實現有很多,我們找一個上面我們分析過的SingleChildRenderObjectWidget,這個類非常簡單,只有一個child,下面看一這個類的原始碼。

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
  final Widget child;
  @override
  SingleChildRenderObjectElement createElement()//註釋1 
    => SingleChildRenderObjectElement(this); 
}
複製程式碼

上面已經說過這個類的繼承關係,這個類繼承RenderObjectWidget,建構函式也很簡單,傳入一個child,重要的是在註釋1處,這個類建立一個類,是SingleChildRenderObjectElement,通過名字猜想,這一定是一個Element了,下面我們就分析一下SingleChildRenderObjectElement類。

這裡驗證了,Weight和Element的關係,一個Widget有一個Element物件,是通過createElement()建立的。

還在分析之前,我們先看一下SingleChildRenderObjectElement的繼承關係,下面是SingleChildRenderObjectElement的繼承關係。

class SingleChildRenderObjectElement extends RenderObjectElement {
}

abstract class RenderObjectElement extends Element {
}
複製程式碼

Flutter渲染 Widget Element RenderObject概述(一)

從上面的繼承關係可以看出來,SingleChildRenderObjectElement繼承RenderObjectElement,而RenderObjectElement是一個Element,大家是不是發現這繼承關係和SingleChildRenderObjectWidget非常像,下面是SingleChildRenderObjectElement的原始碼。

mount方法呼叫

class SingleChildRenderObjectElement extends RenderObjectElement {
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);//註釋2
    _child = updateChild(_child, widget.child, null);//註釋1
  }
}
複製程式碼

建構函式比較簡單,下面我們看一下看,mount方法(到這裡有的人一定會問了,為什麼上來就分析mount方法呢?等下一篇文章,我們分析一下Flutter的啟動過程就清楚了),這個方法是當新建立的元素第一次新增到樹中時,框架會呼叫此函式。

  • 註釋1

    這方法非常關鍵,我們將用一個小節專門去分析,請檢視updateChild分析小節。

  • 註釋2

    註釋2處,呼叫了父類的mount,我們看一下父類的mount的方法。

abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  
  RenderObject _renderObject;//註釋1

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);//註釋2
    attachRenderObject(newSlot);
    _dirty = false;
  }
}
複製程式碼
  • 註釋1

在註釋1處,我們發現RenderObjectElement還持有一個物件,這物件是RenderObject,我們好像明白了一點什麼,Element分別持有Widget和RenderObject,到這裡我們解答了**這個三個物件是什麼關係?**的問題,一個Element包含一個RenderObject和一個Widget

  • 註釋2

重點在註釋2的地方,這裡建立了一個RenderObject,呼叫的是Widget的createRenderObject方法,下面我們看一下attachRenderObject這方法,下面是attachRenderObject的原始碼。

abstract class RenderObjectElement extends Element {
  	@override
    void attachRenderObject(dynamic newSlot) {
      _slot = newSlot;
      _ancestorRenderObjectElement = _findAncestorRenderObjectElement();//註釋1
      _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
      if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);//註釋2
    }
}
複製程式碼
  • 註釋1

註釋1正如他的名字是一樣的,找到了Element樹上的祖先Element,如果祖先不為空,就呼叫insertChildRenderObject方法,這個方法的意思就是把renderObject的child替換成newSlot。

  • 註釋2

用於更新佈局資料的一些資訊,這些資訊對於後面的佈局至關重要。

總結

Flutter渲染 Widget Element RenderObject概述(一)

  1. 當SingleChildRenderObjectElement被SingleChildRenderObjectWidget建立成功之後,系統會呼叫SingleChildRenderObjectElement的mount(),這個方法首先呼叫super.mount(),也就是上圖的第一步。
  2. RenderObjectElement的mount()先建立了一個RenderObject物件,也就是第二步,建立這個物件是在Widget類中建立的。
  3. 第三步,就是把這個將RenderObject新增到指定的位置的渲染樹中。

相關文章