flutter: 根檢視、根元素與根渲染

林鹿發表於2019-08-03

flutter如何建立的檢視樹(WidgetTree),元素樹(ElementTree)及渲染樹(RenderingTree),又是如何更新檢視繪製檢視? 這個問題太大,剛開始一切又都是陌生的,理解起來千頭萬緒,所以先搞清這些樹的根結點的身份是非常必要的。毫無疑問,這些根節點的建立緊密的與初始化過程關聯,而確定了這些根節點之後,遍歷查詢更新就相對清晰了,因為繪製檢視無非也是對樹的遍歷查詢更新操作。

這部分就已經從引擎層進入到了dart層,需要了解的更多的是框架相關的機制,引擎目前用不到了。

環境: flutter sdk v1.7.8+hotfix.4@stable

先不要被Element, RenderObjectElement, RenderObject, Widget,RenderObjectWidget諸多名稱嚇到。與安卓封裝了顯式的啟動執行過程不同,flutter有一個明確的runApp, 這就是進行分析的方便入口。

語言機制

多繼承

需要先了解一下語言層面的一個多繼承機制。雖然這裡用了多繼承這個名詞,但是需要明確dart語言在語法上還是單繼承,也就是隻能extends一個類,其它介面分別再以with串接。

關鍵字宣告

與java不同,dart沒有interface(準確的說是已移除)只有abstractabstract的使用與java並無二致。沒有了interface如何實現多介面物件的宣告?dart用的是mixin關鍵字,所以就方便理解而言,把mixin當作interface, on當作extends(只針對mixin類)即可。與interface不同的是**mixin宣告的類是可以有方法實現體和成員物件的**。

class A extends B implements C, D, E {}
class B {}
interface C {}
interface D {}
interface E {}
複製程式碼

dart等同於:

class A extends B with C, D, E {}
class B {}
mixin C {}
mixin D {}
mixin E {}
複製程式碼

繼承順序

在以上例子中假如B,C,D都有doSomeThing方法

class A extends B with C, D {
  @override
  void doSomeThing() {
    print("A");
    super.doSomeThing();
  }
}

class B {
  @override
  void doSomeThing() {
    print("B");
  }
}

mixin C on B {
  @override
  void doSomeThing() {
    print("C");
    super.doSomeThing();
  }
}

mixin D on B {
  @override
  void doSomeThing() {
    print("D");
    super.doSomeThing();
  }
}

void main() {
  A().doSomeThing();
}
複製程式碼

那麼當執行A.doSomeThing後應該是哪個呼叫順序? 直接給結論:以with宣告的反順序繼承 那麼問題來了:如果沒有C on B會發生什麼? 語言機制問題可參考這篇文章

串連呼叫

需要了解的第2個語法特性是串連呼叫,可以用..操作符串連呼叫類的成員方法:

class F {
  String str;
  String contact(String s) {
    return str + s;
  }

  void assign(String s) {
    str = s;
  }
}

void mai() {
  F f = F()..assign("hello")..contact(' world');
  print(f.str);
}
複製程式碼

需要明確:用了..操作符之後呼叫返回的就是類物件例項,不再是方法的返回值。

初始化呼叫

有了以上基礎(用到語言特性1: mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding)就可以理清runApp入口的呼叫序列:

runApp
  WidgetsFlutterBinding.ensureInitialized
    WidgetsFlutterBinding()
      BindingBase()
        WidgetsBinding.initInstances
          RendererBinding.initInstances
            SemanticsBinding.initInstances
              PaintingBinding.initInstances
                SchedulerBinding.initInstances
                  ServicesBinding.initInstances
                    GestureBinding.initInstances
                      BindingBase.initInstances
複製程式碼

這裡包含了大量的資料初始化,用到一個找一個。 再看整體序列(widgets/binding.dart:786, 用到語言特性2):

runApp
  WidgetsFlutterBinding.ensureInitialized
  WidgetsBinding.attachRootWidget
  WidgetsBinding.scheduleWarmUpFrame
複製程式碼

MyApp例項被傳給了WidgetsBinding.attachRootWidget方法,於是分析其呼叫序列:

runApp
  WidgetsBinding.attachRootWidget
    RenderObjectToWidgetAdapter()
    RenderObjectToWidgetAdapter.attachToRenderTree
      RenderObjectToWidgetAdapter.createElement
      RenderObjectToWidgetElement<RenderBox>.assignOwner
      BuildOwner.buildScope
      RenderObjectToWidgetElement<RenderBox>.mount
複製程式碼

需要注意RenderObjectToWidgetAdapter是一個RenderObjectWidget型別,它用建構函式child: rootWidget, 持有了外部傳入的rootWidget作為它的子檢視。 RenderObjectToWidgetAdapter.createElement建立的元素被賦值給了_renderViewElement_renderViewElementWidgetsBinding例項持有。

元素關聯渲染

那根渲染又是何時建立的呢?繼續看mount的呼叫序列:

RenderObjectToWidgetElement<RenderBox>.mount
  RootRenderObjectElement.mount
    RenderObjectElement.mount
      RenderObjectWidget.createRenderObject => RenderObjectToWidgetAdapter.createRenderObject
複製程式碼

這裡容易讓人誤導,呼叫createRenderObject的其實是RenderObjectElement持有的RenderObjectWidget, 而元素RenderObjectToWidgetElement正是RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this)(widgets/binding.dart:833)所建立,這裡的this其實就是RenderObjectToWidgetAdapter,所以根渲染是RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;(widgets/bindings.836),可見根渲染不是在此時建立的,而是預先被賦值僅在此時返回的。

各種根節點

由此可見MyApp作為外部傳入的rootWidget不是真正的根檢視,真正的根檢視其實是RenderObjectToWidgetAdapter, 它被RenderObjectToWidgetElement<RenderBox>持有(一個Element持有一個Widget), 而這個Element被全域性WidgetsBinding例項持有,所以根元素為RenderObjectToWidgetElement<RenderBox>

RenderObjectElementmount的時機建立了一個RenderObject例項並持有,而RenderObjectToWidgetElementRenderObjectElement的子類,建立的RenderObject具體型別為RenderObjectWithChildMixin<RenderBox>,所以它才是最終的根渲染。

有了rootElement就可以找到rootWidgetrootRenderObject, 元素樹,檢視樹與渲染樹由此建立起來。

根渲染建立

回到RenderObjectToWidgetAdapter呼叫建構函式的地方,傳入的containerRenderingBindingRenderView get renderView => _pipelineOwner.rootNode;(rendering/binding.dart:162, attachRootWidgetWidgetsBinding的方法,但 mixin WidgetsBinding on RendererBinding,所以可以引用到RenderingBinding的成員)。

那麼rootRenderObject,也就是上面的RenderView, 作為RenderObjectWithChildMixin<RenderBox>的子類(class RenderView with RenderObjectWithChildMixin<RenderBox>),又是在什麼時機建立的?跟蹤下來正是在初始化呼叫中:

runApp
  WidgetsFlutterBinding.ensureInitialized
    WidgetsFlutterBinding()
      BindingBase()
        WidgetsBinding.initInstances
          RendererBinding.initInstances
            _pipelineOwner = PipelineOwner(
            RendererBinding.initRenderView
              renderView = RenderView()
                _pipelineOwner.rootNode = value;
複製程式碼

也就是說WidgetBinding把RendererBinding(mixin WidgetBinding with RendererBinding)的renderView作為了根渲染,而它實際是_pipelineOwner.rootNode

至此,我們便知道了所有節點遍歷的起點。

相關文章