深入學(xué)習(xí)Flutter的運(yùn)行機(jī)制

關(guān)注個(gè)人簡(jiǎn)介,面試不迷路~

main入口啟動(dòng)

  • Flutter的主入口在"lib/main.dart"的main()函數(shù)中。在Flutter應(yīng)用中,main()函數(shù)最簡(jiǎn)單的實(shí)現(xiàn)如下:
    void main() {
      runApp(MyApp());
    }
    
  • 可以看到main()函數(shù)只調(diào)用了一個(gè)runApp()方法,runApp()方法中都做了什么:
    void runApp(Widget app) {
      //初始化操作
      WidgetsFlutterBinding.ensureInitialized()
        //頁(yè)面渲染
        ..attachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    
*   參數(shù)`app`是一個(gè)Widget,是Flutter應(yīng)用啟動(dòng)后要展示的第一個(gè)Widget。
*   `WidgetsFlutterBinding`正是綁定widget 框架和Flutter engine的橋梁。
  • ensureInitialized()方法
    class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
      static WidgetsBinding ensureInitialized() {
        if (WidgetsBinding.instance == null)
          WidgetsFlutterBinding();
        return WidgetsBinding.instance;
      }
    }
    
*   可以看到`WidgetsFlutterBinding`繼承自`BindingBase` 并混入了很多`Binding`,在介紹`Binding`之前先介紹一下`Window`,`Window`的官方解釋:The most basic interface to the host operating system's user interface.
  • Window正是Flutter Framework連接宿主操作系統(tǒng)的接口??匆幌?code>Window類的部分定義:
    class Window {
      // 當(dāng)前設(shè)備的DPI,即一個(gè)邏輯像素顯示多少物理像素,數(shù)字越大,顯示效果就越精細(xì)保真。
      // DPI是設(shè)備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5 
      double get devicePixelRatio => _devicePixelRatio;
      // 繪制回調(diào)  
      VoidCallback get onDrawFrame => _onDrawFrame;
      // 發(fā)送平臺(tái)消息
      void sendPlatformMessage(String name,
                               ByteData data,
                               PlatformMessageResponseCallback callback) ;
      ... //其它屬性及回調(diào)
    }
    
*   `Window`類包含了當(dāng)前設(shè)備和系統(tǒng)的一些信息以及Flutter Engine的一些回調(diào)。現(xiàn)在回來看看`WidgetsFlutterBinding`混入的各種Binding。通過查看這些 Binding的源碼,可以發(fā)現(xiàn)這些Binding中基本都是監(jiān)聽并處理`Window`對(duì)象的一些事件,然后將這些事件按照Framework的模型包裝、抽象然后分發(fā)。可以看到`WidgetsFlutterBinding`正是粘連Flutter engine與上層Framework的“膠水”。
  • 看看attachToRenderTree的源碼實(shí)現(xiàn):
    RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
      if (element == null) {
        ...// 代碼處理
      } else {
        ...// 代碼處理
      }
      return element;
    }
    
  • 該方法負(fù)責(zé)創(chuàng)建根element,即 RenderObjectToWidgetElement,并且將element與widget 進(jìn)行關(guān)聯(lián),即創(chuàng)建出 widget樹對(duì)應(yīng)的element樹。
    • 如果element 已經(jīng)創(chuàng)建過了,則將根element 中關(guān)聯(lián)的widget 設(shè)為新的,由此可以看出element 只會(huì)創(chuàng)建一次,后面會(huì)進(jìn)行復(fù)用。那么BuildOwner是什么呢?其實(shí)他就是widget framework的管理類,它跟蹤哪些widget需要重新構(gòu)建。

頁(yè)面渲染

  • 回到runApp的實(shí)現(xiàn)中,當(dāng)調(diào)用完attachRootWidget后,最后一行會(huì)調(diào)用 WidgetsFlutterBinding 實(shí)例的 scheduleWarmUpFrame() 方法,該方法的實(shí)現(xiàn)在SchedulerBinding 中,它被調(diào)用后會(huì)立即進(jìn)行一次繪制(而不是等待"vsync"信號(hào)),在此次繪制結(jié)束前,該方法會(huì)鎖定事件分發(fā),也就是說在本次繪制結(jié)束完成之前Flutter將不會(huì)響應(yīng)各種事件,這可以保證在繪制過程中不會(huì)再觸發(fā)新的重繪。
  • 下面是scheduleWarmUpFrame() 方法的部分實(shí)現(xiàn)(省略了無關(guān)代碼):
    void scheduleWarmUpFrame() {
      Timer.run(() {
        handleBeginFrame(null); 
      });
      Timer.run(() {
        handleDrawFrame();  
        resetEpoch();
      });
      // 鎖定事件
      lockEvents(() async {
        await endOfFrame;
        Timeline.finishSync();
      });
     ...
    }
    
*   可以看到該方法中主要調(diào)用了`handleBeginFrame()` 和 `handleDrawFrame()` 兩個(gè)方法,在看這兩個(gè)方法之前我們首先了解一下Frame 和 FrameCallback 的概念:
*   Frame: 一次繪制過程,我們稱其為一幀。Flutter engine受顯示器垂直同步信號(hào)"VSync"的驅(qū)使不斷的觸發(fā)繪制。我們之前說的Flutter可以實(shí)現(xiàn)60fps(Frame Per-Second),就是指一秒鐘可以觸發(fā)60次重繪,F(xiàn)PS值越大,界面就越流暢。
*   FrameCallback:`SchedulerBinding` 類中有三個(gè)FrameCallback回調(diào)隊(duì)列, 在一次繪制過程中,這三個(gè)回調(diào)隊(duì)列會(huì)放在不同時(shí)機(jī)被執(zhí)行:
    *   1.  `transientCallbacks`:用于存放一些臨時(shí)回調(diào),一般存放動(dòng)畫回調(diào)??梢酝ㄟ^`SchedulerBinding.instance.scheduleFrameCallback` 添加回調(diào)。
    *   2.  `persistentCallbacks`:用于存放一些持久的回調(diào),不能在此類回調(diào)中再請(qǐng)求新的繪制幀,持久回調(diào)一經(jīng)注冊(cè)則不能移除。`SchedulerBinding.instance.addPersitentFrameCallback()`,這個(gè)回調(diào)中處理了布局與繪制工作。
    *   3.  `postFrameCallbacks`:在Frame結(jié)束時(shí)只會(huì)被調(diào)用一次,調(diào)用后會(huì)被系統(tǒng)移除,可由 `SchedulerBinding.instance.addPostFrameCallback()` 注冊(cè),注意,不要在此類回調(diào)中再觸發(fā)新的Frame,這可以會(huì)導(dǎo)致循環(huán)刷新。
*   自行查看`handleBeginFrame()` 和 `handleDrawFrame()` 兩個(gè)方法的源碼,可以發(fā)現(xiàn)前者主要是執(zhí)行了`transientCallbacks`隊(duì)列,而后者執(zhí)行了 `persistentCallbacks` 和 `postFrameCallbacks` 隊(duì)列。

頁(yè)面繪制

  • 渲染和繪制邏輯在RendererBinding中實(shí)現(xiàn),查看其源碼,發(fā)現(xiàn)在其initInstances()方法中有如下代碼:
    void initInstances() {
      ... //省略無關(guān)代碼
          
      //監(jiān)聽Window對(duì)象的事件  
      ui.window
        ..onMetricsChanged = handleMetricsChanged
        ..onTextScaleFactorChanged = handleTextScaleFactorChanged
        ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
        ..onSemanticsAction = _handleSemanticsAction;
       
      //添加PersistentFrameCallback    
      addPersistentFrameCallback(_handlePersistentFrameCallback);
    }
    
*   看最后一行,通過`addPersistentFrameCallback` 向`persistentCallbacks`隊(duì)列添加了一個(gè)回調(diào) `_handlePersistentFrameCallback`:

```
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
}

```
  • 該方法直接調(diào)用了RendererBindingdrawFrame()方法

flushLayout()

  • 代碼如下所示
    void flushLayout() {
       ...
        while (_nodesNeedingLayout.isNotEmpty) {
          final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
          _nodesNeedingLayout = <RenderObject>[];
          for (RenderObject node in 
               dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
            if (node._needsLayout && node.owner == this)
              node._layoutWithoutResize();
          }
        }
      } 
    }
    
  • 源碼很簡(jiǎn)單,該方法主要任務(wù)是更新了所有被標(biāo)記為“dirty”的RenderObject的布局信息。主要的動(dòng)作發(fā)生在node._layoutWithoutResize()方法中,該方法中會(huì)調(diào)用performLayout()進(jìn)行重新布局。

flushCompositingBits()

  • 代碼如下所示
    void flushCompositingBits() {
      _nodesNeedingCompositingBitsUpdate.sort(
          (RenderObject a, RenderObject b) => a.depth - b.depth
      );
      for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
        if (node._needsCompositingBitsUpdate && node.owner == this)
          node._updateCompositingBits(); //更新RenderObject.needsCompositing屬性值
      }
      _nodesNeedingCompositingBitsUpdate.clear();
    }
    
  • 檢查RenderObject是否需要重繪,然后更新RenderObject.needsCompositing屬性,如果該屬性值被標(biāo)記為true則需要重繪。

flushPaint()

  • 代碼如下所示
    void flushPaint() {
     ...
      try {
        final List<RenderObject> dirtyNodes = _nodesNeedingPaint; 
        _nodesNeedingPaint = <RenderObject>[];
        // 反向遍歷需要重繪的RenderObject
        for (RenderObject node in 
             dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
          if (node._needsPaint && node.owner == this) {
            if (node._layer.attached) {
              // 真正的繪制邏輯  
              PaintingContext.repaintCompositedChild(node);
            } else {
              node._skippedPaintingOnLayer();
            }
          }
        }
      } 
    }
    
*   該方法進(jìn)行了最終的繪制,可以看出它不是重繪了所有 `RenderObject`,而是只重繪了需要重繪的 `RenderObject`。真正的繪制是通過`PaintingContext.repaintCompositedChild()`來繪制的,該方法最終會(huì)調(diào)用Flutter engine提供的Canvas API來完成繪制。

compositeFrame()

  • 代碼如下所示
    void compositeFrame() {
      ...
      try {
        final ui.SceneBuilder builder = ui.SceneBuilder();
        final ui.Scene scene = layer.buildScene(builder);
        if (automaticSystemUiAdjustment)
          _updateSystemChrome();
        ui.window.render(scene); //調(diào)用Flutter engine的渲染API
        scene.dispose(); 
      } finally {
        Timeline.finishSync();
      }
    }
    
  • 這個(gè)方法中有一個(gè)Scene對(duì)象,Scene對(duì)象是一個(gè)數(shù)據(jù)結(jié)構(gòu),保存最終渲染后的像素信息。
    • 這個(gè)方法將Canvas畫好的Scene傳給window.render()方法,該方法會(huì)直接將scene信息發(fā)送給Flutter engine,最終由engine將圖像畫在設(shè)備屏幕上。

最后

  • 需要注意:由于RendererBinding只是一個(gè)mixin,而with它的是WidgetsBinding,所以需要看看WidgetsBinding中是否重寫該方法,查看WidgetsBindingdrawFrame()方法源碼:
    @override
    void drawFrame() {
     ...//省略無關(guān)代碼
      try {
        if (renderViewElement != null)
          buildOwner.buildScope(renderViewElement); 
        super.drawFrame(); //調(diào)用RendererBinding的drawFrame()方法
        buildOwner.finalizeTree();
      } 
    }
    
  • 發(fā)現(xiàn)在調(diào)用RendererBinding.drawFrame()方法前會(huì)調(diào)用 buildOwner.buildScope() (非首次繪制),該方法會(huì)將被標(biāo)記為“dirty” 的 element 進(jìn)行 rebuild() 。
?著作權(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)容