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

前言

前面幾篇文章介紹了Flutter框架的渲染流水線,window,初始化以及Widget,ElementRenderObject體系。其中對(duì)Widget,ElementRenderObject的介紹主要是一些靜態(tài)的說明,了解了以上這些技術(shù)點(diǎn)之后,在這篇文章里我們會(huì)通過動(dòng)態(tài)運(yùn)行的方式來介紹一下Flutter框架是如何運(yùn)行的。
從之前介紹的渲染流水線可以知道,這個(gè)過程大致可以分為兩段操作。第一段是從State.setState()到去engine那里請(qǐng)求一幀,第二段就是Vsync信號(hào)到來以后渲染流水線開始重建新的一幀最后送入engine去顯示。我們先來看第一段Flutter框架都做了什么。

調(diào)度之前

先看一下State.setState()

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

這里會(huì)調(diào)用到ElementmarkNeedsBuild()函數(shù)。

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

Element首先看自己是不是active的狀態(tài),不是的話就直接返回了,如果是“臟”(dirty)的狀態(tài)也直接返回,不是的話會(huì)置上這個(gè)狀態(tài)然后調(diào)用BuildOwnerscheduleBuildFor()函數(shù),這個(gè)BuildOwner我們之前介紹過,它的實(shí)例是在WidgetsBinding初始化的時(shí)候構(gòu)建的。每個(gè)Element的都會(huì)持有BuildOwner的引用。由其父Elementmount的時(shí)候設(shè)置。

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

BuildOwner會(huì)維護(hù)一個(gè)_dirtyElements列表,所有被標(biāo)記為“臟”(dirty)的element都會(huì)被添加進(jìn)去。在此之前會(huì)調(diào)用onBuildScheduled()。這個(gè)函數(shù)是WidgetsBinding初始化的時(shí)候設(shè)置給BuildOwner的,對(duì)應(yīng)的是WidgetsBinding._handleBuildScheduled()。

void _handleBuildScheduled() {
    ensureVisualUpdate();
  }

這里會(huì)調(diào)用到ensureVisualUpdate()。這個(gè)函數(shù)定義在SchedulerBinding里的

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

函數(shù)ensureVisualUpdate()會(huì)判斷當(dāng)前調(diào)度所處的狀態(tài),如果是在idle(空閑)或者postFrameCallbacks運(yùn)行狀態(tài)則調(diào)用scheduleFrame()。其他狀態(tài)則直接返回。下面這三個(gè)狀態(tài)正是渲染流水線運(yùn)行的時(shí)候。

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

在函數(shù)scheduleFrame()里我們看到了熟悉的window。這里就是通知engine去調(diào)度一幀的地方了。調(diào)度之后會(huì)置上_hasScheduledFrame標(biāo)志位,避免重復(fù)請(qǐng)求。另外一個(gè)標(biāo)志位_framesEnabled是代表當(dāng)前app的狀態(tài),或者說其所處的生命周期是否允許刷新界面。這個(gè)狀態(tài)有四種:resumedinactive,pausedsuspending。

  • resumed:app可見且可以響應(yīng)用戶輸入。
  • inactive:app不能響應(yīng)用戶輸入,例如在Android上彈出系統(tǒng)對(duì)話框。
  • paused:app對(duì)用戶不可見。
  • suspending:app掛起??這個(gè)狀態(tài)貌似Android和iOS都沒有上報(bào)。

_framesEnabled只有在resumedinactive狀態(tài)下才為true。也就是說,只有在這兩個(gè)狀態(tài)下Flutter框架才會(huì)刷新頁(yè)面。

至此第一階段,也就是調(diào)度之前的工作做完了。看起來比較簡(jiǎn)單,主要就是把需要重建的Element放入_dirtyElements列表。接下來Flutter框架會(huì)等待Vsync信號(hào)到來以后engine回調(diào)框架,這就是第二段要做的事情了。

Vsync到來之后

我們之前說過Vsync信號(hào)到來之后,engin會(huì)按順序回調(diào)window的兩個(gè)回調(diào)函數(shù):onBeginFrame()onDrawFrame()。這兩個(gè)回調(diào)是SchedulerBinding初始化的時(shí)候設(shè)置給window的。對(duì)應(yīng)的是SchedulerBinding.handleBeginFrame()SchedulerBinding.handleDrawFrame()。

onBeginFrame

這個(gè)回調(diào)會(huì)直接走到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;
    }
  }

這個(gè)函數(shù)主要是在依次回調(diào)“Transient”回調(diào)函數(shù),這些回調(diào)函數(shù)是在調(diào)度之前設(shè)置在SchedulerBinding里的,這里的“Transient”意思是臨時(shí)的,或者說是一次性的。原因是這些回調(diào)函數(shù)只會(huì)被調(diào)用一次。注意看代碼里_transientCallbacks被置為空Map了。如果想在下一幀再次調(diào)用的話需要提前重新設(shè)置回調(diào)。這些回調(diào)主要和動(dòng)畫有關(guān)系。也就是渲染流水線里的第一階段,動(dòng)畫(Animate)階段。關(guān)于動(dòng)畫后續(xù)我會(huì)再寫文章從框架角度分析一下動(dòng)畫的機(jī)制。

在運(yùn)行回調(diào)之前_schedulerPhase的狀態(tài)被設(shè)置為SchedulerPhase.transientCallbacks。回調(diào)處理完以后狀態(tài)更新至SchedulerPhase.midFrameMicrotasks意思是接下來會(huì)處理微任務(wù)隊(duì)列。處理完微任務(wù)以后,engine會(huì)接著回調(diào)onDrawFrame()。

onDrawFrame

這個(gè)回調(diào)會(huì)直接走到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里按順序處理了兩類回調(diào),一類叫“Persistent”回調(diào),另一類叫“Post-Frame”回調(diào)。

“Persistent”字面意思是永久的。這類回調(diào)一旦注冊(cè)以后是不能取消的。主要用來驅(qū)動(dòng)渲染流水線。渲染流水線的構(gòu)建(build),布局(layout)和繪制(paint)階段都是在其中一個(gè)回調(diào)里的。

“Post-Frame”回調(diào)主要是在新幀渲染完成以后的一類調(diào)用,此類回調(diào)只會(huì)被調(diào)用一次。

在運(yùn)行“Persistent”回調(diào)之前_schedulerPhase狀態(tài)變?yōu)?code>SchedulerPhase.persistentCallbacks。在運(yùn)行“Post-Frame”回調(diào)之前_schedulerPhase狀態(tài)變?yōu)?code>SchedulerPhase.postFrameCallbacks。最終狀態(tài)變?yōu)?code>SchedulerPhase.idle。

這里我們主要關(guān)注一個(gè)“Persistent”回調(diào):WidgetsBinding.drawFrame()。這個(gè)函數(shù)是在RendererBinding初始化的時(shí)候加入到“Persistent”回調(diào)的。

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

這里首先會(huì)調(diào)用buildOwner.buildScope(renderViewElement)。其入?yún)?code>renderViewElement是element tree的根節(jié)點(diǎn)。此時(shí)渲染流水線就進(jìn)入了構(gòu)建(build)階段。接下來調(diào)用了super.drawFrame()。這個(gè)函數(shù)定義在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的手里,渲染流水線就進(jìn)入了布局(layout)階段和繪制(paint)階段。關(guān)于最后這兩個(gè)階段本篇不做詳細(xì)介紹。這里大家只要知道繪制完成以后Flutter框架最終會(huì)調(diào)用window.render(scene)將新幀的數(shù)據(jù)送入engine顯示到屏幕。

最后調(diào)用buildOwner.finalizeTree();。這個(gè)函數(shù)的作用是清理不再需要的Element節(jié)點(diǎn)。在element tree更新以后可能有些節(jié)點(diǎn)就不再需要掛載在樹上了,在finalizeTree()的時(shí)候會(huì)將這些節(jié)點(diǎn)及其子節(jié)點(diǎn)unmount。

構(gòu)建(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;
     
    }
  }

還記得在調(diào)度幀之前會(huì)把需要更新的Element標(biāo)記為“臟”(dirty)并放入BuildOwner_dirtyElements列表。這里Flutter會(huì)先按照深度給這個(gè)列表排個(gè)序。因?yàn)?code>Element在重建的時(shí)候其子節(jié)點(diǎn)也都會(huì)重建,這樣如果父節(jié)點(diǎn)和子節(jié)點(diǎn)都為“臟”的話,先重建父節(jié)點(diǎn)就避免了子節(jié)點(diǎn)的重復(fù)重建。

排完序就是遍歷_dirtyElements列表。依次調(diào)用Element.rebuild()。這個(gè)函數(shù)又會(huì)調(diào)用到Element.performRebuild()。我們之前介紹Element的時(shí)候說過performRebuild()由其子類實(shí)現(xiàn)。

我們之前的出發(fā)點(diǎn)是State.setState()。那就先看看StatefulElement如何做的。它的performRebuild()是在其父類ComponentElement里:

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

回憶一下ComponentElement。這個(gè)build()函數(shù)最終會(huì)調(diào)用到State.build()了。返回的就是我們自己實(shí)例化的Widget。拿到這個(gè)新Widget就去調(diào)用updateChild()。之前在講Element的時(shí)候我們介紹過updateChild()這個(gè)函數(shù)。由增,刪,改這么幾種情況,對(duì)于MyWidget,從State.setState()過來是屬于改的情況。此時(shí)會(huì)調(diào)用child.update(newWidget);。這個(gè)update()函數(shù)又是由各個(gè)Element子類實(shí)現(xiàn)的。這里我們只列舉幾個(gè)比較典型的。

StatefulElementStatelessElementupdate()函數(shù)最終都會(huì)調(diào)用基類Elementrebuild()函數(shù)。好像在兜圈圈的感覺。。。

RenderObjectElementupdate()函數(shù)就比較簡(jiǎn)單了

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

更新只是調(diào)用了一下RenderObjectWidget.updateRenderObject()。這個(gè)函數(shù)我們之前介紹過,只是把新的配置設(shè)置到現(xiàn)有的RenderObject上。

回到上面那個(gè)兜圈圈的問題。理清這里的調(diào)用關(guān)系的關(guān)鍵就是要搞清楚是此時(shí)的Element是在對(duì)自己進(jìn)行操作還是對(duì)孩子進(jìn)行操作。假設(shè)我們有這樣的一個(gè)三層element tree進(jìn)行更新重建。

  父(StatefulElement)

  子(StatefulElement)

  孫(LeafRenderObjectElement)

那么從父節(jié)點(diǎn)開始,調(diào)用順序如下:

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

可見構(gòu)建(build)過程是從需要重建的Element節(jié)點(diǎn)開始一層層向下逐個(gè)更新子節(jié)點(diǎn)。直到遇到葉子節(jié)點(diǎn)為止。

至此渲染流水線的構(gòu)建(build)階段就跑完了。接下來就由pipelineOwner驅(qū)動(dòng)開始布局(layout)和繪制(paint)階段了。這兩個(gè)階段留待以后再給大家介紹一下。

總結(jié)

本篇文章從我們熟悉的State.setState()函數(shù)出發(fā),大致介紹了Flutter框架是如何運(yùn)行渲染流水線的??傮w來說其運(yùn)行時(shí)分為兩個(gè)階段,向engine調(diào)度幀之前和Vsync信號(hào)到來engine回調(diào)Flutter框架之后。剩余篇幅則是以更新Element為例介紹了一下渲染流水線的構(gòu)建(build)階段都做了一些什么事情。限于篇幅,沒有更多涉及Element的新增和刪除步驟。大家感興趣的話可以直接看源碼來了解相關(guān)信息。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容