Flutter實(shí)戰(zhàn) Flutter運(yùn)行機(jī)制-從啟動(dòng)到顯示

2021-03-09 14:42 更新

本節(jié)我們主要介紹一下 Flutter 從啟動(dòng)到顯示的過程。

#啟動(dòng)

Flutter 的入口在"lib/main.dart"的main()函數(shù)中,它是 Dart 應(yīng)用程序的起點(diǎn)。在 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()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

參數(shù)app是一個(gè) widget,它是 Flutter 應(yīng)用啟動(dòng)后要展示的第一個(gè) Widget。而WidgetsFlutterBinding正是綁定 widget 框架和 Flutter engine 的橋梁,定義如下:

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)的接口。我們看一下Window類的部分定義:

class Window {

    
  // 當(dāng)前設(shè)備的DPI,即一個(gè)邏輯像素顯示多少物理像素,數(shù)字越大,顯示效果就越精細(xì)保真。
  // DPI是設(shè)備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5 
  double get devicePixelRatio => _devicePixelRatio;

  
  // Flutter UI繪制區(qū)域的大小
  Size get physicalSize => _physicalSize;


  // 當(dāng)前系統(tǒng)默認(rèn)的語言Locale
  Locale get locale;

    
  // 當(dāng)前系統(tǒng)字體縮放比例。  
  double get textScaleFactor => _textScaleFactor;  

    
  // 當(dāng)繪制區(qū)域大小改變回調(diào)
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale發(fā)生變化回調(diào)
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系統(tǒng)字體縮放變化回調(diào)
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 繪制前回調(diào),一般會(huì)受顯示器的垂直同步信號(hào)VSync驅(qū)動(dòng),當(dāng)屏幕刷新時(shí)就會(huì)被調(diào)用
  FrameCallback get onBeginFrame => _onBeginFrame;
  // 繪制回調(diào)  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 點(diǎn)擊或指針事件回調(diào)
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 調(diào)度Frame,該方法執(zhí)行后,onBeginFrame和onDrawFrame將緊接著會(huì)在合適時(shí)機(jī)被調(diào)用,
  // 此方法會(huì)直接調(diào)用Flutter engine的Window_scheduleFrame方法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新應(yīng)用在GPU上的渲染,此方法會(huì)直接調(diào)用Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';


  // 發(fā)送平臺(tái)消息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 平臺(tái)通道消息處理回調(diào)  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;

  
  ... //其它屬性及回調(diào)

   
}

可以看到Window類包含了當(dāng)前設(shè)備和系統(tǒng)的一些信息以及 Flutter Engine 的一些回調(diào)。現(xiàn)在我們?cè)倩貋砜纯?code>WidgetsFlutterBinding混入的各種 Binding。通過查看這些 Binding 的源碼,我們可以發(fā)現(xiàn)這些 Binding 中基本都是監(jiān)聽并處理Window對(duì)象的一些事件,然后將這些事件按照 Framework 的模型包裝、抽象然后分發(fā)??梢钥吹?code>WidgetsFlutterBinding正是粘連 Flutter engine 與上層 Framework 的“膠水”。

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

WidgetsFlutterBinding.ensureInitialized()負(fù)責(zé)初始化一個(gè)WidgetsBinding的全局單例,緊接著會(huì)調(diào)用WidgetsBindingattachRootWidget方法,該方法負(fù)責(zé)將根 Widget 添加到RenderView上,代碼如下:

void attachRootWidget(Widget rootWidget) {
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView, 
    debugShortDescription: '[root]',
    child: rootWidget
  ).attachToRenderTree(buildOwner, renderViewElement);
}

注意,代碼中的有renderViewrenderViewElement兩個(gè)變量,renderView是一個(gè)RenderObject,它是渲染樹的根,而renderViewElementrenderView對(duì)應(yīng)的Element對(duì)象,可見該方法主要完成了根 widget 到根 RenderObject再到根Element的整個(gè)關(guān)聯(lián)過程。我們看看attachToRenderTree的源碼實(shí)現(xiàn):

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);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  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)建。

#渲染

回到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)刷新。

現(xiàn)在請(qǐng)讀者自行查看handleBeginFrame()handleDrawFrame() 兩個(gè)方法的源碼,可以發(fā)現(xiàn)前者主要是執(zhí)行了transientCallbacks隊(duì)列,而后者執(zhí)行了 persistentCallbackspostFrameCallbacks 隊(duì)列。

#繪制

渲染和繪制邏輯在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);
}

我們看最后一行,通過addPersistentFrameCallbackpersistentCallbacks隊(duì)列添加了一個(gè)回調(diào) _handlePersistentFrameCallback:

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

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

void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout(); //布局
  pipelineOwner.flushCompositingBits(); //重繪之前的預(yù)處理操作,檢查RenderObject是否需要重繪
  pipelineOwner.flushPaint(); // 重繪
  renderView.compositeFrame(); // 將需要繪制的比特?cái)?shù)據(jù)發(fā)給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();
      }
    }
  } 
}

源碼很簡(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() 。

#總結(jié)

本節(jié)介紹了 Flutter APP 從啟動(dòng)到顯示到屏幕上的主流程,讀者可以結(jié)合前面章節(jié)對(duì) Widget、Element 以及 RenderObject 的介紹來加強(qiáng)細(xì)節(jié)理解。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)