Flutter實戰(zhàn) Flutter運行機制-從啟動到顯示

2021-03-09 14:42 更新

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

#啟動

Flutter 的入口在"lib/main.dart"的main()函數(shù)中,它是 Dart 應用程序的起點。在 Flutter 應用中,main()函數(shù)最簡單的實現(xiàn)如下:

  1. void main() {
  2. runApp(MyApp());
  3. }

可以看main()函數(shù)只調用了一個runApp()方法,我們看看runApp()方法中都做了什么:

  1. void runApp(Widget app) {
  2. WidgetsFlutterBinding.ensureInitialized()
  3. ..attachRootWidget(app)
  4. ..scheduleWarmUpFrame();
  5. }

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

  1. class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  2. static WidgetsBinding ensureInitialized() {
  3. if (WidgetsBinding.instance == null)
  4. WidgetsFlutterBinding();
  5. return WidgetsBinding.instance;
  6. }
  7. }

可以看到WidgetsFlutterBinding繼承自BindingBase 并混入了很多Binding,在介紹這些Binding之前我們先介紹一下Window,下面是Window的官方解釋:

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

很明顯,Window正是 Flutter Framework 連接宿主操作系統(tǒng)的接口。我們看一下Window類的部分定義:

  1. class Window {
  2. // 當前設備的DPI,即一個邏輯像素顯示多少物理像素,數(shù)字越大,顯示效果就越精細保真。
  3. // DPI是設備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5
  4. double get devicePixelRatio => _devicePixelRatio;
  5. // Flutter UI繪制區(qū)域的大小
  6. Size get physicalSize => _physicalSize;
  7. // 當前系統(tǒng)默認的語言Locale
  8. Locale get locale;
  9. // 當前系統(tǒng)字體縮放比例。
  10. double get textScaleFactor => _textScaleFactor;
  11. // 當繪制區(qū)域大小改變回調
  12. VoidCallback get onMetricsChanged => _onMetricsChanged;
  13. // Locale發(fā)生變化回調
  14. VoidCallback get onLocaleChanged => _onLocaleChanged;
  15. // 系統(tǒng)字體縮放變化回調
  16. VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  17. // 繪制前回調,一般會受顯示器的垂直同步信號VSync驅動,當屏幕刷新時就會被調用
  18. FrameCallback get onBeginFrame => _onBeginFrame;
  19. // 繪制回調
  20. VoidCallback get onDrawFrame => _onDrawFrame;
  21. // 點擊或指針事件回調
  22. PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  23. // 調度Frame,該方法執(zhí)行后,onBeginFrame和onDrawFrame將緊接著會在合適時機被調用,
  24. // 此方法會直接調用Flutter engine的Window_scheduleFrame方法
  25. void scheduleFrame() native 'Window_scheduleFrame';
  26. // 更新應用在GPU上的渲染,此方法會直接調用Flutter engine的Window_render方法
  27. void render(Scene scene) native 'Window_render';
  28. // 發(fā)送平臺消息
  29. void sendPlatformMessage(String name,
  30. ByteData data,
  31. PlatformMessageResponseCallback callback) ;
  32. // 平臺通道消息處理回調
  33. PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  34. ... //其它屬性及回調
  35. }

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

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

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

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

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

  1. RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
  2. if (element == null) {
  3. owner.lockState(() {
  4. element = createElement();
  5. assert(element != null);
  6. element.assignOwner(owner);
  7. });
  8. owner.buildScope(element, () {
  9. element.mount(null, null);
  10. });
  11. } else {
  12. element._newWidget = this;
  13. element.markNeedsBuild();
  14. }
  15. return element;
  16. }

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

#渲染

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

  1. void scheduleWarmUpFrame() {
  2. ...
  3. Timer.run(() {
  4. handleBeginFrame(null);
  5. });
  6. Timer.run(() {
  7. handleDrawFrame();
  8. resetEpoch();
  9. });
  10. // 鎖定事件
  11. lockEvents(() async {
  12. await endOfFrame;
  13. Timeline.finishSync();
  14. });
  15. ...
  16. }

可以看到該方法中主要調用了handleBeginFrame()handleDrawFrame() 兩個方法,在看這兩個方法之前我們首先了解一下Frame 和 FrameCallback 的概念:

  • Frame: 一次繪制過程,我們稱其為一幀。Flutter engine 受顯示器垂直同步信號"VSync"的驅使不斷的觸發(fā)繪制。我們之前說的 Flutter 可以實現(xiàn) 60fps(Frame Per-Second),就是指一秒鐘可以觸發(fā)60次重繪,F(xiàn)PS 值越大,界面就越流暢。
  • FrameCallback:SchedulerBinding 類中有三個 FrameCallback 回調隊列, 在一次繪制過程中,這三個回調隊列會放在不同時機被執(zhí)行:
    1. transientCallbacks:用于存放一些臨時回調,一般存放動畫回調??梢酝ㄟ^SchedulerBinding.instance.scheduleFrameCallback 添加回調。
    2. persistentCallbacks:用于存放一些持久的回調,不能在此類回調中再請求新的繪制幀,持久回調一經注冊則不能移除。SchedulerBinding.instance.addPersitentFrameCallback(),這個回調中處理了布局與繪制工作。
    3. postFrameCallbacks:在 Frame 結束時只會被調用一次,調用后會被系統(tǒng)移除,可由 SchedulerBinding.instance.addPostFrameCallback() 注冊,注意,不要在此類回調中再觸發(fā)新的 Frame,這可以會導致循環(huán)刷新。

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

#繪制

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

  1. void initInstances() {
  2. ... //省略無關代碼
  3. //監(jiān)聽Window對象的事件
  4. ui.window
  5. ..onMetricsChanged = handleMetricsChanged
  6. ..onTextScaleFactorChanged = handleTextScaleFactorChanged
  7. ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
  8. ..onSemanticsAction = _handleSemanticsAction;
  9. //添加PersistentFrameCallback
  10. addPersistentFrameCallback(_handlePersistentFrameCallback);
  11. }

我們看最后一行,通過addPersistentFrameCallbackpersistentCallbacks隊列添加了一個回調 _handlePersistentFrameCallback:

  1. void _handlePersistentFrameCallback(Duration timeStamp) {
  2. drawFrame();
  3. }

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

  1. void drawFrame() {
  2. assert(renderView != null);
  3. pipelineOwner.flushLayout(); //布局
  4. pipelineOwner.flushCompositingBits(); //重繪之前的預處理操作,檢查RenderObject是否需要重繪
  5. pipelineOwner.flushPaint(); // 重繪
  6. renderView.compositeFrame(); // 將需要繪制的比特數(shù)據發(fā)給GPU
  7. pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  8. }

我們看看這些方法分別做了什么:

#flushLayout()

  1. void flushLayout() {
  2. ...
  3. while (_nodesNeedingLayout.isNotEmpty) {
  4. final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
  5. _nodesNeedingLayout = <RenderObject>[];
  6. for (RenderObject node in
  7. dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
  8. if (node._needsLayout && node.owner == this)
  9. node._layoutWithoutResize();
  10. }
  11. }
  12. }
  13. }

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

#flushCompositingBits()

  1. void flushCompositingBits() {
  2. _nodesNeedingCompositingBitsUpdate.sort(
  3. (RenderObject a, RenderObject b) => a.depth - b.depth
  4. );
  5. for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
  6. if (node._needsCompositingBitsUpdate && node.owner == this)
  7. node._updateCompositingBits(); //更新RenderObject.needsCompositing屬性值
  8. }
  9. _nodesNeedingCompositingBitsUpdate.clear();
  10. }

檢查RenderObject是否需要重繪,然后更新RenderObject.needsCompositing屬性,如果該屬性值被標記為true則需要重繪。

#flushPaint()

  1. void flushPaint() {
  2. ...
  3. try {
  4. final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
  5. _nodesNeedingPaint = <RenderObject>[];
  6. // 反向遍歷需要重繪的RenderObject
  7. for (RenderObject node in
  8. dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
  9. if (node._needsPaint && node.owner == this) {
  10. if (node._layer.attached) {
  11. // 真正的繪制邏輯
  12. PaintingContext.repaintCompositedChild(node);
  13. } else {
  14. node._skippedPaintingOnLayer();
  15. }
  16. }
  17. }
  18. }
  19. }

該方法進行了最終的繪制,可以看出它不是重繪了所有 RenderObject,而是只重繪了需要重繪的 RenderObject。真正的繪制是通過PaintingContext.repaintCompositedChild()來繪制的,該方法最終會調用 Flutter engine 提供的 Canvas API 來完成繪制。

#compositeFrame()

  1. void compositeFrame() {
  2. ...
  3. try {
  4. final ui.SceneBuilder builder = ui.SceneBuilder();
  5. final ui.Scene scene = layer.buildScene(builder);
  6. if (automaticSystemUiAdjustment)
  7. _updateSystemChrome();
  8. ui.window.render(scene); //調用Flutter engine的渲染API
  9. scene.dispose();
  10. } finally {
  11. Timeline.finishSync();
  12. }
  13. }

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

#最后

需要注意的是:由于RendererBinding只是一個 mixin,而 with 它的是WidgetsBinding,所以我們需要看看WidgetsBinding中是否重寫該方法,查看WidgetsBindingdrawFrame()方法源碼:

  1. @override
  2. void drawFrame() {
  3. ...//省略無關代碼
  4. try {
  5. if (renderViewElement != null)
  6. buildOwner.buildScope(renderViewElement);
  7. super.drawFrame(); //調用RendererBinding的drawFrame()方法
  8. buildOwner.finalizeTree();
  9. }
  10. }

我們發(fā)現(xiàn)在調用RendererBinding.drawFrame()方法前會調用 buildOwner.buildScope() (非首次繪制),該方法會將被標記為“dirty” 的 element 進行 rebuild() 。

#總結

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

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號