Flutter框架分析(四)-- Flutter框架的運行

Flutter框架分析分析系列文章:bash

《Flutter框架分析(一)-- 總覽和Window》app

《Flutter框架分析(二)-- 初始化》框架

《Flutter框架分析(三)-- Widget,Element和RenderObject》less

《Flutter框架分析(四)-- Flutter框架的運行》函數

《Flutter框架分析(五)-- 動畫》佈局

《Flutter框架分析(六)-- 佈局》post

前言

前面幾篇文章介紹了Flutter框架的渲染流水線,window,初始化以及WidgetElementRenderObject體系。其中對WidgetElementRenderObject的介紹主要是一些靜態的說明,瞭解了以上這些技術點以後,在這篇文章裏咱們會經過動態運行的方式來介紹一下Flutter框架是如何運行的。 從以前介紹的渲染流水線能夠知道,這個過程大體能夠分爲兩段操做。第一段是從State.setState()到去engine那裏請求一幀,第二段就是Vsync信號到來之後渲染流水線開始重建新的一幀最後送入engine去顯示。咱們先來看第一段Flutter框架都作了什麼。動畫

調度以前

先看一下State.setState()ui

void setState(VoidCallback fn) {
    
    final dynamic result = fn() as dynamic;
  
    _element.markNeedsBuild();
  }
複製代碼

這裏會調用到ElementmarkNeedsBuild()函數。this

void markNeedsBuild() {
    if (!_active)
      return;
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
複製代碼

Element首先看本身是否是active的狀態,不是的話就直接返回了,若是是「髒」(dirty)的狀態也直接返回,不是的話會置上這個狀態而後調用BuildOwnerscheduleBuildFor()函數,這個BuildOwner咱們以前介紹過,它的實例是在WidgetsBinding初始化的時候構建的。每一個Element的都會持有BuildOwner的引用。由其父Elementmount的時候設置。

void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }
複製代碼

BuildOwner會維護一個_dirtyElements列表,全部被標記爲「髒」(dirty)的element都會被添加進去。在此以前會調用onBuildScheduled()。這個函數是WidgetsBinding初始化的時候設置給BuildOwner的,對應的是WidgetsBinding._handleBuildScheduled()

void _handleBuildScheduled() {
    ensureVisualUpdate();
  }
複製代碼

這裏會調用到ensureVisualUpdate()。這個函數定義在SchedulerBinding裏的

void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }
複製代碼

函數ensureVisualUpdate()會判斷當前調度所處的狀態,若是是在idle(空閒)或者postFrameCallbacks運行狀態則調用scheduleFrame()。其餘狀態則直接返回。下面這三個狀態正是渲染流水線運行的時候。

void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
複製代碼

在函數scheduleFrame()裏咱們看到了熟悉的window。這裏就是通知engine去調度一幀的地方了。調度以後會置上_hasScheduledFrame標誌位,避免重複請求。另一個標誌位_framesEnabled是表明當前app的狀態,或者說其所處的生命週期是否容許刷新界面。這個狀態有四種:resumedinactivepausedsuspending

  • resumed:app可見且能夠響應用戶輸入。
  • inactive:app不能響應用戶輸入,例如在Android上彈出系統對話框。
  • paused:app對用戶不可見。
  • suspending:app掛起??這個狀態貌似Android和iOS都沒有上報。

_framesEnabled只有在resumedinactive狀態下才爲true。也就是說,只有在這兩個狀態下Flutter框架纔會刷新頁面。

至此第一階段,也就是調度以前的工做作完了。看起來比較簡單,主要就是把須要重建的Element放入_dirtyElements列表。接下來Flutter框架會等待Vsync信號到來之後engine回調框架,這就是第二段要作的事情了。

Vsync到來以後

咱們以前說過Vsync信號到來以後,engin會按順序回調window的兩個回調函數:onBeginFrame()onDrawFrame()。這兩個回調是SchedulerBinding初始化的時候設置給window的。對應的是SchedulerBinding.handleBeginFrame()SchedulerBinding.handleDrawFrame()

onBeginFrame

這個回調會直接走到SchedulerBinding.handleBeginFrame()

void handleBeginFrame(Duration rawTimeStamp) {
   ...
    _hasScheduledFrame = false;
    try {
      // TRANSIENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      _removedIds.clear();
    } finally {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }
複製代碼

這個函數主要是在依次回調「Transient」回調函數,這些回調函數是在調度以前設置在SchedulerBinding裏的,這裏的「Transient」意思是臨時的,或者說是一次性的。緣由是這些回調函數只會被調用一次。注意看代碼裏_transientCallbacks被置爲空Map了。若是想在下一幀再次調用的話須要提早從新設置回調。這些回調主要和動畫有關係。也就是渲染流水線裏的第一階段,動畫(Animate)階段。關於動畫後續我會再寫文章從框架角度分析一下動畫的機制。

在運行回調以前_schedulerPhase的狀態被設置爲SchedulerPhase.transientCallbacks。回調處理完之後狀態更新至SchedulerPhase.midFrameMicrotasks意思是接下來會處理微任務隊列。處理完微任務之後,engine會接着回調onDrawFrame()

onDrawFrame

這個回調會直接走到SchedulerBinding.handleDrawFrame()

void handleDrawFrame() {
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      _currentFrameTimeStamp = null;
    }
  }
複製代碼

handleDrawFrame裏按順序處理了兩類回調,一類叫「Persistent」回調,另外一類叫「Post-Frame」回調。

「Persistent」字面意思是永久的。這類回調一旦註冊之後是不能取消的。主要用來驅動渲染流水線。渲染流水線的構建(build),佈局(layout)和繪製(paint)階段都是在其中一個回調裏的。

「Post-Frame」回調主要是在新幀渲染完成之後的一類調用,此類回調只會被調用一次。

在運行「Persistent」回調以前_schedulerPhase狀態變爲SchedulerPhase.persistentCallbacks。在運行「Post-Frame」回調以前_schedulerPhase狀態變爲SchedulerPhase.postFrameCallbacks。最終狀態變爲SchedulerPhase.idle

這裏咱們主要關注一個「Persistent」回調:WidgetsBinding.drawFrame()。這個函數是在RendererBinding初始化的時候加入到「Persistent」回調的。

void drawFrame() {
   try {
    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
    buildOwner.finalizeTree();
  } finally {
     ...
  }
}
複製代碼

這裏首先會調用buildOwner.buildScope(renderViewElement)。其入參renderViewElement是element tree的根節點。此時渲染流水線就進入了構建(build)階段。接下來調用了super.drawFrame()。這個函數定義在RendererBinding中。

void drawFrame() {
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  renderView.compositeFrame(); // this sends the bits to the GPU
  pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
複製代碼

能夠看出渲染流水線的接力棒傳到了pipelineOwner的手裏,渲染流水線就進入了佈局(layout)階段和繪製(paint)階段。關於最後這兩個階段本篇不作詳細介紹。這裏你們只要知道繪製完成之後Flutter框架最終會調用window.render(scene)將新幀的數據送入engine顯示到屏幕。

最後調用buildOwner.finalizeTree();。這個函數的做用是清理再也不須要的Element節點。在element tree更新之後可能有些節點就再也不須要掛載在樹上了,在finalizeTree()的時候會將這些節點及其子節點unmount。

構建(build)階段

void buildScope(Element context, [VoidCallback callback]) {
    try {
      _scheduledFlushDirtyElements = true;
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
      }
      
    } finally {
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
     
    }
  }
複製代碼

還記得在調度幀以前會把須要更新的Element標記爲「髒」(dirty)並放入BuildOwner_dirtyElements列表。這裏Flutter會先按照深度給這個列表排個序。由於Element在重建的時候其子節點也都會重建,這樣若是父節點和子節點都爲「髒」的話,先重建父節點就避免了子節點的重複重建。

排完序就是遍歷_dirtyElements列表。依次調用Element.rebuild()。這個函數又會調用到Element.performRebuild()。咱們以前介紹Element的時候說過performRebuild()由其子類實現。

咱們以前的出發點是State.setState()。那就先看看StatefulElement如何作的。它的performRebuild()是在其父類ComponentElement裏:

void performRebuild() {
    Widget built;
    built = build();
    try {
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
      ...
    }
  }
複製代碼

回憶一下ComponentElement。這個build()函數最終會調用到State.build()了。返回的就是咱們本身實例化的Widget。拿到這個新Widget就去調用updateChild()。以前在講Element的時候咱們介紹過updateChild()這個函數。由增,刪,改這麼幾種狀況,對於MyWidget,從State.setState()過來是屬於改的狀況。此時會調用child.update(newWidget);。這個update()函數又是由各個Element子類實現的。這裏咱們只列舉幾個比較典型的。

StatefulElementStatelessElementupdate()函數最終都會調用基類Elementrebuild()函數。好像在兜圈圈的感受。。。

RenderObjectElementupdate()函數就比較簡單了

void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
複製代碼

更新只是調用了一下RenderObjectWidget.updateRenderObject()。這個函數咱們以前介紹過,只是把新的配置設置到現有的RenderObject上。

回到上面那個兜圈圈的問題。理清這裏的調用關係的關鍵就是要搞清楚是此時的Element是在對本身進行操做仍是對孩子進行操做。假設咱們有這樣的一個三層element tree進行更新重建。

父(StatefulElement)

  子(StatefulElement)

  孫(LeafRenderObjectElement)
複製代碼

那麼從父節點開始,調用順序以下:

父.rebuild()--->父.performRebuild()--->父.updateChild()--->子.update()--->子.rebuild()--->子.performRebuild()--->子.updateChild()--->孫.update()。

可見構建(build)過程是從須要重建的Element節點開始一層層向下逐個更新子節點。直到遇到葉子節點爲止。

至此渲染流水線的構建(build)階段就跑完了。接下來就由pipelineOwner驅動開始佈局(layout)和繪製(paint)階段了。這兩個階段留待之後再給你們介紹一下。

總結

本篇文章從咱們熟悉的State.setState()函數出發,大體介紹了Flutter框架是如何運行渲染流水線的。整體來講其運行時分爲兩個階段,向engine調度幀以前和Vsync信號到來engine回調Flutter框架以後。剩餘篇幅則是以更新Element爲例介紹了一下渲染流水線的構建(build)階段都作了一些什麼事情。限於篇幅,沒有更多涉及Element的新增和刪除步驟。你們感興趣的話能夠直接看源碼來了解相關信息。

相關文章
相關標籤/搜索