Flutter筆記——runApp發生了什麼(原始碼學習)

悟篤篤發表於2020-01-18

終於放假啦,不用一直寫業務程式碼了,也終於有點時間可以整理整理筆記啦。
我在這裡先給大家拜個早年,恭祝大家新春快樂,吉祥安康啦!

拜年.gif

Flutter系列學習筆記

main

FlutterFramework在Flutter層的專案入口是main函式,預設生成的函式如下

void main() {
  runApp(MyApp());
}
複製程式碼

1 runApp

runApp是一個頂級函式,接受一個Widget作為rootWidget,這中間發生了什麼嘞?

下文以圖片、原始碼和文字解析的方式輔助學習。由於內容較長,需要分為幾個部分學習,並且序列圖、記錄點和實際執行順序有出入,下文排序主要是為了能夠由淺入深的鋪開runApp過程中的知識點,幫助更好的理解。

2.1 過程一

runApp過程一.png

  1. main函式,呼叫runApp(Widget)函式
    ///同main函式,runApp函式也是一個頂級函式
    void main() {
      runApp(MyApp());
    }
    複製程式碼
  2. 初始化WidgetsFlutterBinding.instance
    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()
        ..scheduleAttachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    複製程式碼
  3. ensureInitialized函式會建立一個WidgetsFlutterBinding的單例WidgetsBinding.instance
    class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
    
      ///確認WidgetsBinding.instance是否建立成功,flutter framework工作是是否完成
      static WidgetsBinding ensureInitialized() {
        if (WidgetsBinding.instance == null)
          WidgetsFlutterBinding();
        return WidgetsBinding.instance;
      }
    }
    複製程式碼
  4. WidgetsFlutterBinding通過mixIn語法繼承了7個BindingBase子類,完成整個FlutterFramework層次的系統功能初始化。mixIn語法可參考我的這篇文章HelloDart-MixIn,土話記錄多繼承機制
    abstract class BindingBase {
      
      ///會按照WidgetsFlutterBinding整合順序,以此呼叫每個WidgetsFlutterBinding子類的 
      ///initInstances和initServiceExtensions初始化函式。
      BindingBase() {
        developer.Timeline.startSync('Framework initialization');
    
        assert(!_debugInitialized);
        initInstances();
        assert(_debugInitialized);
    
        assert(!_debugServiceExtensionsRegistered);
        initServiceExtensions();
        assert(_debugServiceExtensionsRegistered);
    
        developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    
        developer.Timeline.finishSync();
      }
    }
    複製程式碼
  5. initInstancesinitServiceExtensions該部分比較複雜,這裡先不學習了。簡單介紹一下BindingBase系列子類的作用,下列內容我也是看註釋的,如有錯誤煩請指出
    • WidgetsFlutterBinding:將FlutterFramework繫結到FlutterEngine上面
    • GestureBinding:繫結手勢系統。
    • ServicesBinding:主要作用與defaultBinaryMessenger有關,用於和native通訊相關。
    • SchedulerBinding:改類也繼承了ServicesBinding,主要用於排程幀渲染相關事件。
    • PaintingBinding:和painting庫繫結
    • SemanticsBinding:將語義層和FlutterEngine繫結起來。
    • RendererBinding:將渲染樹與FlutterEngine繫結起來。
    • WidgetsBinding:將Widget層與FlutterEngine繫結起來。 再次重申一下,上述內容只是看註釋得來的,如果錯漏,感謝指出

初始化完WidgetsFlutterBinding.instance及一堆系統Binding之後,便開始下一步操作了。

void runApp(Widget app) {
    WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

這裡使用級聯語法分別呼叫了scheduleAttachRootWidget(Widget)scheduleWarmUpFrame函式,具體過程見下文

2.2 過程二

runApp過程二.png

  1. scheduleAttachRootWidget(app):通過級聯的方式,生成了WidgetsFlutterBinding.instance靜態例項並初始化一干系統功能之後,呼叫WidgetsFlutterBinding.scheduleAttachRootWidget(Widget root)函式。
    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()
        ..scheduleAttachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    複製程式碼
  2. attachRootWidget(Widget):這裡非同步呼叫WidgetsBinding.attachRootWidget(Widget rootWidget)函式,只為了儘快顯示Flutter專案的UI畫面。
      void scheduleAttachRootWidget(Widget rootWidget) {
        Timer.run(() {
          attachRootWidget(rootWidget);
        });
      }
    複製程式碼
  3. scheduleWarmUpFrame():在上面第二點中,非同步呼叫attachRootWidget函式,只為了儘快呼叫該函式,去顯示一個幀畫面。這個函式的詳細流程按下不表,等以後再學習。
      /// Schedule a frame to run as soon as possible, rather than waiting for
      /// the engine to request a frame in response to a system "Vsync" signal.
      void scheduleWarmUpFrame() {
        ...
      }
    複製程式碼
  4. RenderObjectToWidgetAdapter:接著第2點,attachRootWidget函式作用就是將根Widget、根Element與根RenderObject三個跟物件繫結起來,並將唯一的BuildOwner物件引用作為根物件的持有物件,通過繼承關係層層傳遞。看程式碼
    ///虛擬碼
    class WidgetsBinding ...{
      void attachRootWidget(Widget rootWidget) {
        //建立一個RenderObjectToWidgetAdapter物件,泛型T是RenderBox型別,
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
          container: renderView,
          debugShortDescription: '[root]',//renderView[註釋1]是連線到物理裝置輸出層的渲染樹物件
          child: rootWidget,  //根Widget樹
        ).attachToRenderTree(buildOwner, renderViewElement);
      }    
    }
    class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
      /// Creates a bridge from a [RenderObject] to an [Element] tree.
      ///
      /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
      RenderObjectToWidgetAdapter({
        this.child,
        this.container,
        this.debugShortDescription,
      }) : super(key: GlobalObjectKey(container));
    
      /// The widget below this widget in the tree.
      final Widget child;
    
      /// The [RenderObject] that is the parent of the [Element] created by this widget.
      final RenderObjectWithChildMixin<T> container;
    
      @override
      RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
    
      @override
      RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
    
      @override
      void updateRenderObject(BuildContext context, RenderObject renderObject) { }
    
      ///
      RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
        if (element == null) {
          owner.lockState(() {
            element = createElement();
            assert(element != null);
            element.assignOwner(owner);
          });
          owner.buildScope(element, () {
            element.mount(null, null);
          });
          // This is most likely the first time the framework is ready to produce
          // a frame. Ensure that we are asked for one.
          SchedulerBinding.instance.ensureVisualUpdate();
        } else {
          element._newWidget = this;
          element.markNeedsBuild();
        }
        return element;
      }
    }
    複製程式碼
  5. attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]):**這裡面關於element的建立先忽略,在步驟3中學習。**該部分原始碼在第四點中,我們可以看到RenderObjectToWidgetAdapter會建立一個RenderObjectToWidgetElement物件,作為WidgetsBinding中的_renderViewElement物件。該_renderViewElement物件也就是整個Element樹中的跟物件,其持有根Widget、根RenderView與BuildOwner物件。

2.3 過程三

過程三主要分析RenderObjectToWidgetAdapter.attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ])函式,期間還有一些BaseBinding內容摻雜進來,比較複雜。

runApp過程三.png

  • BuildOnwer.lockState(fn):

      ///該函式中的_debugStateLocked 布林值,每次呼叫該函式_debugStateLockLevel都會+1,
      ///callback執行完之後減1,如果在_debugStateLocked等於true期間呼叫setState就會丟擲異常。
      void lockState(void callback()) {
        assert(callback != null);
        assert(_debugStateLockLevel >= 0);
        assert(() {
          _debugStateLockLevel += 1;
          return true;
        }());
        try {
          callback();
        } finally {
          assert(() {
            _debugStateLockLevel -= 1;
            return true;
          }());
        }
        assert(_debugStateLockLevel >= 0);
      }
    複製程式碼
  • RenderObjectToWidgetAdapter.createElement():會建立一個RenderObjectToWidgetElement物件。

    Element依賴關係圖.png

    這裡先不去細看其眾多父類中的屬性和操作

  • element.assignOwner(owner):將owner:BuildOnwer賦值給element,該owner:BuildOnwer是整個Element樹的統一管理者。

  • element.mount(null, null):上圖忽略了BuildOwner.buildScope(Element context, [ VoidCallback callback ])函式了,該函式作用在於將一個Element新增進去構建域中,並呼叫VoidCallback函式作為回撥。
    這裡要著重看下element物件一眾父類中的mount函式了

      owner.buildScope(element, () {
        element.mount(null, null);
      });
    class RenderObjectToWidgetElement ...{
      @override
      void mount(Element parent, dynamic newSlot) {
        assert(parent == null);
        super.mount(parent, newSlot);
        //這裡的child暫時為null,忽略rebuild函式
        _rebuild();
      }
    }
    abstract class RootRenderObjectElement extends RenderObjectElement {
    
      @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
      }
    }
    ///RenderObjectElement類中,最重要的屬性,_renderObject
    ///是該根Element的RenderObject物件,該RenderObject類儲存
    ///了Element的位置資訊,Skia是一個2D渲染框架,基於笛卡爾座標軸
    ///RenderObject也實現了基本的渲染功能。
    abstract class RenderObjectElement extends Element {
      /// The underlying [RenderObject] for this element.
      @override
      RenderObject get renderObject => _renderObject;
      RenderObject _renderObject;
      @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        //這個回到步驟二中的RenderObjectToWidgetAdapter類,RendererBinding類初始化之後有個PipelineOwner
        //物件,PipelineOwner物件中有個rootNode物件,就是該_renderObject了
        _renderObject = widget.createRenderObject(this);
        //這裡newSlot等於null,忽略
        attachRenderObject(newSlot);
        //將dirty置為false,
        _dirty = false;
      }
    }
    class Element ...{
      @mustCallSuper
      void mount(Element parent, dynamic newSlot) {
        //一頓賦值
        _parent = parent;
        _slot = newSlot;
        _depth = _parent != null ? _parent.depth + 1 : 1;
        _active = true;
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner;
        if (widget.key is GlobalKey) {
          final GlobalKey key = widget.key;
          key._register(this);
        }
        _updateInheritance();
        assert(() {
          _debugLifecycleState = _ElementLifecycle.active;
          return true;
        }());
      }    
    }
    複製程式碼

    mount(Element parent, dynamic newSlot)掛載函式在於將Element掛載到Element樹上去,如果有parent屬性就賦值,掛載之後Element的狀態變為active。

  • SchedulerBinding.instance.ensureVisualUpdate()

      void ensureVisualUpdate() {
        switch (schedulerPhase) {
          case SchedulerPhase.idle:
          case SchedulerPhase.postFrameCallbacks:
            scheduleFrame();
            return;
          case SchedulerPhase.transientCallbacks:
          case SchedulerPhase.midFrameMicrotasks:
          case SchedulerPhase.persistentCallbacks:
            return;
        }
      }
    複製程式碼
  • SchedulerBinding.scheduleFrame():接著便是呼叫window.scheduleFrame()函式了,這是一個native函式,純FlutterFramework部分線索就斷了。

      void scheduleFrame() {
        if (_hasScheduledFrame || !_framesEnabled)
          return;
        assert(() {
          if (debugPrintScheduleFrameStacks)
            debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
          return true;
        }());
        ensureFrameCallbacksRegistered();
        window.scheduleFrame();
        _hasScheduledFrame = true;
      }
    複製程式碼

3 小結

這裡做個runApp的簡單過程圖,隱藏了許多細節

runApp簡單過程圖.png

  1. FlutterFramework的Dart部分,程式入口是main函式。
  2. 呼叫runApp(Widget)函式傳入一個Widget作為根Widget。
  3. Widget只是一個配置類,不是實際的UI元素。
  4. runApp通過WidgetsFlutterBindingmixIn繼承一眾父類進行初始化。
  5. 其中,RendererBinding父類中的renderView物件,是實際的渲染物件。
  6. 通過RenderObjectToWidgetAdapter類,生成一個RenderObjectToWidgetElement<RenderBox>型別的Element作為根Element,並讓Widget、renderView和BuildOwner和根Element產生關係。
  7. 掛載根Element,呼叫SchedulerBinding.instance.ensureVisualUpdate()函式,等待下一幀渲染。
  8. scheduleAttachRootWidget是一個耗時操作,非同步執行。runApp會優先呼叫scheduleWarmUpFrame()渲染預熱幀。

上述文章是筆者通過對Flutter1.12版本原始碼進行學習的總結,如有錯漏,還煩請指出糾正,十分感謝。 另外本文只是runApp的原始碼進行了跟蹤學習,Flutter到底是如何渲染到Native裝置、還有BaseBinding系列子類作用等,等之後篇學習筆記哈。

相關文章