Flutter核心原理之Flutter運行機制-從啟動到顯示


一,概述

Flutter的入口在"lib/main.dart"的main()函數中,它是Dart應用程序的起點。在Flutter應用中,main()函數如下:

 

#runApp()方法:

參數app是一個Widget,它是Flutter應用啟動后要展示的第一個Widget。而WidgetsFlutterBinding正是綁定Widget 框架和Flutter engine的橋梁,定義如下:

 

WidgetsFlutterBinding繼承自 BindingBase 並混入了很多Binding,在介紹這些Binding之前我們先介紹一下Window對象。我們看看Window的官方解釋:

The most basic interface to the host operating system's user interface.

Window正是Flutter Framework連接宿主操作系統的接口。
class Window {
 
  // 當前設備的DPI,即一個邏輯像素顯示多少物理像素,數字越大,顯示效果就越精細保真。
  // DPI是設備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5 
  double get devicePixelRatio => _devicePixelRatio;
 
  // Flutter UI繪制區域的大小
  Size get physicalSize => _physicalSize;
 
  // 當前系統默認的語言Locale
  Locale get locale;
 
  // 當前系統字體縮放比例。  
  double get textScaleFactor => _textScaleFactor;  
 
  // 當繪制區域大小改變回調
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale發生變化回調
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系統字體縮放變化回調
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 繪制前回調,一般會受顯示器的垂直同步信號VSync驅動,當屏幕刷新時就會被調用
  FrameCallback get onBeginFrame => _onBeginFrame;
  // 繪制回調  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 點擊或指針事件回調
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 調度Frame,該方法執行后,onBeginFrame和onDrawFrame將緊接着會在合適時機被調用,
  // 此方法會直接調用Flutter engine的Window_scheduleFrame方法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新應用在GPU上的渲染,此方法會直接調用Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';
 
  // 發送平台消息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 平台通道消息處理回調  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
 
  ... //其它屬性及回調
 
}

Window類包含了當前設備和系統的一些信息以及Flutter Engine的一些回調。現在我們再回來看看WidgetsFlutterBinding混入的各種Binding。通過查看這些 Binding的源碼,我們可以發現這些Binding中基本都是監聽並處理Window對象的一些事件,然后將這些事件按照Framework模型包裝抽象然后分發。可以看到WidgetsFlutterBinding正是粘連Flutter engine與上層Framework的”膠水“。

  • GestureBinding: 提供了window.onPointerDataPacket 回調,綁定Framework手勢子系統,是Framework事件模型與底層事件的綁定入口。
  • ServicesBinding:提供了window.onPlatformMessage 回調, 用於綁定平台消息通道(message channel),主要處理原生和Flutter通信。
  • SchedulerBinding:提供了window.onBeginFrame和window.onDrawFrame回調,監聽刷新事件,綁定Framework繪制調度子系統。
  • PaintingBinding: 綁定繪制庫,主要用於處理圖片緩存。
  • SemanticsBinding:語義化層與Flutter engine的橋梁,主要是輔助功能的底層支持。
  • RendererBinding: 提供了window.onMetricsChanged 、window.onTextScaleFactorChanged 等回調。它是渲染樹與Flutter engine的橋梁。
  • WidgetsBinding: 提供了window.onLocaleChanged、onBuildScheduled 等回調。它Flutter Widget層與engine的橋梁。

WidgetsFlutterBinding.ensureInitialized()負責初始化一個WidgetsBinding的全局單例,緊接着會調用WidgetsBindingattachRootWidget方法,該方法負責將根Widget添加到RenderView上,代碼如下:

代碼中的有renderViewrenderViewElement兩個變量,renderView是一個RenderObject,它是渲染樹的根,而renderViewElementrenderView對應的Element對象,可見該方法主要完成了 根Widget 到根 RenderObject再到更Element的整個關聯過程。我們看看attachToRenderTree的源碼實現:

該方法負責創建根 Element,即 RenderObjectToWidgetElement,並且將 Element 與 Widget 進行關聯,即創建出 WidgetTree 對應的 ElementTree。如果 Element 已經創建過了,則將根 Element 中關聯的 Widget 設為新的,由此可以看出 Element 只會創建一次,后面會進行復用。那么BuildOwner是什么呢?其實他就是Widget framework的管理類,它跟蹤哪些Widget需要重新構建。

二,渲染:

回到runApp的實現中,當調用完attachRootWidget后,最后一行會調用 WidgetsFlutterBinding 實例的 scheduleWarmUpFrame() 方法,該方法的實現在 SchedulerBinding 中,它被調用后會立即進行一次繪制(而不是等待"Vsync" 信號),在此次繪制結束前,該方法會鎖定事件分發,也就是說在本次繪制結束完成之前Flutter將不會響應各種事件,這可以保證在繪制過程中不會再觸發新的重繪。下面是scheduleWarmUpFrame() 方法的實現:

 
  /// 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.
  ///
  /// This is used during application startup so that the first frame (which is
  /// likely to be quite expensive) gets a few extra milliseconds to run.
  ///
  /// Locks events dispatching until the scheduled frame has completed.
  ///
  /// If a frame has already been scheduled with [scheduleFrame] or
  /// [scheduleForcedFrame], this call may delay that frame.
  ///
  /// If any scheduled frame has already begun or if another
  /// [scheduleWarmUpFrame] was already called, this call will be ignored.
  ///
  /// Prefer [scheduleFrame] to update the display in normal operation.
  void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;
 
    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });
 
    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

該方法中主要調用了handleBeginFrame() 和 handleDrawFrame() 兩個方法.

  • Frame 和c 的概念:   Frame: 一次繪制過程,我們稱其為一幀。Flutter engine受顯示器垂直同步信號"VSync"的趨勢不斷的觸發繪制。我們之前說的Flutter可以實現60fps(Frame Per-Second),就是指一秒鍾可以觸發60次重繪,FPS值越大,界面就越流暢。   FrameCallback:SchedulerBinding 類中有三個FrameCallback回調隊列, 在一次繪制過程中,這三個回調隊列會放在不同時機被執行:
      • transientCallbacks:用於存放一些臨時回調,一般存放動畫回調。可以通過SchedulerBinding.instance.scheduleFrameCallback 添加回調。    
      • persistentCallbacks:用於存放一些持久的回調,不能在此類回調中再請求新的繪制幀,持久回調一經注冊則不能移除。SchedulerBinding.instance.addPersitentFrameCallback(),這個回調中處理了布局與繪制工作。    
      • postFrameCallbacks:在Frame結束時只會被調用一次,調用后會被系統移除,可由 SchedulerBinding.instance.addPostFrameCallback() 注冊,注意,不要在此類回調中再觸發新的Frame,這可以會導致循環刷新。

    • handleBeginFrame() 和 handleDrawFrame() 兩個方法的源碼,可以發現前者主要是執行了transientCallbacks隊列,而后者執行了 persistentCallbacks 和 postFrameCallbacks 隊列。

三,繪制

渲染和繪制邏輯在RenderBinding 中實現,查看其源發,發現在其initInstances()方法中有如下代碼:

void initInstances() {
  ... //省略無關代碼
 
  //監聽Window對象的事件  
  ui.window
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsAction = _handleSemanticsAction;
 
  //添加PersistentFrameCallback    
  addPersistentFrameCallback(_handlePersistentFrameCallback);
}

通過addPersistentFrameCallback 向persistentCallbacks隊列添加了一個回調 _handlePersistentFrameCallback:

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

該方法直接調用了RenderBindingdrawFrame()方法:

void drawFrame() {
 assert(renderView != null);
 pipelineOwner.flushLayout(); //布局
 pipelineOwner.flushCompositingBits(); //重繪之前的預處理操作,檢查RenderObject是否需要重繪
 pipelineOwner.flushPaint(); // 重繪
 renderView.compositeFrame(); // 將需要繪制的比特數據發給GPU
 pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
  • 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();
      }
    }
  } 
}

該方法主要任務是更新了所有被標記為“dirty”的RenderObject的布局信息。主要的動作發生在node._layoutWithoutResize()方法中,該方法中會調用performLayout()進行重新布局。 

  • 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屬性,如果該屬性值被標記為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();
        }
      }
    }
  } 
}

該方法進行了最終的繪制,可以看出它不是重繪了所有 RenderObject,而是只重繪了需要重繪的 RenderObject。真正的繪制是通過PaintingContext.repaintCompositedChild()來繪制的,該方法最終會調用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); //調用Flutter engine的渲染API
    scene.dispose(); 
  } finally {
    Timeline.finishSync();
  }
}

方法中有一個Scene對象,Scene對象是一個數據結構,保存最終渲染后的像素信息。這個方法將Canvas畫好的Scene傳給window.render()方法,該方法會直接將scene信息發送給Flutter engine,最終又engine將圖像畫在設備屏幕上。 

五,注意:

由於RenderBinding只是一個mixin,而with它的是WidgetBinding,所以我們需要看看WidgetBinding中是否重寫該方法,查看WidgetBindingdrawFrame()方法源碼:

@override
void drawFrame() {
 ...//省略無關代碼
  try {
    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement); 
    super.drawFrame(); //調用RenderBinding的drawFrame()方法
    buildOwner.finalizeTree();
  } 
}

在調用RenderBinding.drawFrame()方法前會調用 buildOwner.buildScope() (非首次繪制),該方法會將被標記為“dirty” 的 Element 進行 rebuild() 


---------------------
原文:https://blog.csdn.net/qq_39969226/article/details/94217758


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM