Flutter UI架構啟動過程-11

AidenCang 發表於 2019-12-03

概述

上一篇文章中我們介紹了android系統呼叫Engine:Run方法的過程,最終呼叫到FlutterUI層中的main()函式,FlutterUI框架開始初始化,FlutterEngine初始化過程中已經把Window物件初始化完成並且和Engine相關連,FlutterEngine:Run的過程中初始化FlutterUImain(),把FlutterUI層處理的相關邏輯註冊到Window.dart類的本地方法中,建立FlutterEngine和FlutterUI的通訊過程,把FlutterEngine相關的事件分解在不同的Binding類的處理邏輯上。

核心邏輯

1.FlutterEngin初始化,建立ui.Window.dart物件

2.FlutterEngine:run方法呼叫FlutterUI層的處理邏輯,使用不同的BaseBinding子類來分解引擎事件

3.註冊不同的BaseBinding處理邏輯到ui.Window層的本地方法和邏輯處理

4.如何自己實現FlutterUI層的程式碼,彙總一個簡單的UI給FlutterEngine渲染到Android的SurfaceView上

在Engine原始碼分析中,我們知道,Flutter是通過widow層和Flutter 框架層通訊,Widow是上下層通訊的樞紐,啟動承上啟下的作用,那麼Widow最小的可執行單元是什麼??

flutter 引擎初始化完成之後,會呼叫Window中的onBeginFrame 方法來構建一幀,構建完成一幀之後呼叫ui.window.render(sceneBuilder.build());傳遞給Flutter的Engine中,第二幀是通過scheduleFrame不斷的調動來改變整個Widget的構建過程,不斷的改變Widget生成的渲染物件,傳遞給Flutter引擎,就可以實現相關的程式碼介面的變動,頁面跳轉等。

記著下面三個方法,Widget的所用呼叫過程都是在為構建一幀在做準備

ui.window.onBeginFrame = beginFrame;
ui.window.render(sceneBuilder.build());
ui.window.scheduleFrame();
複製程式碼

ui.Window類是Flutter引擎和FlutterUI框架的介面,FlutterUI是怎麼啟動起來的???

1.主要完成的是FlutterUI層和FlutterEngine層的繫結操作ui.Window

2.分解系統成的事件

GestureBinding
ServicesBinding
SchedulerBinding
PaintingBinding,
SemanticsBinding
RendererBinding
WidgetsBinding
複製程式碼

3.提供一個渲染物件個給ui.window.render(sceneBuilder.build()); 呼叫Engine中的渲染引擎

4.初始化平臺Plugin介面

4.初始化多語言,多區域

window 事件是怎麼分解給不同的Binding進行處理的??

1.FlutterEngine通過Widow類和FlutterUI進行通訊,包括Widget的繪製和生命週期管理

2.Window類把相關的事件回撥和呼叫方法返回給不同的Bindings類進行解耦,每個部分完成相關的操作

3.FlutterUI層通過runApp包Flutter相關的部分提交給系統框架和系統框架層進行通訊

4.通過直接使用window類進行Frame的繪製,就能夠對Flutter層進行理解,FlutterUI層其實是Window構造的一個擴充套件和細化,

Flutter呼叫時序.png

Flutter UI架構啟動過程-11

核心步驟:

Widgets層和window層解耦是通過繼承BindingBasemixin類來繼續的,不同的Bindings類繼承BindingBase,


/// A concrete binding for applications based on the Widgets framework.
///
/// This is the glue that binds the framework to the Flutter engine.
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}
複製程式碼

WidgetsFlutterBinding 實現了BindingBase,在BindingBase的構造方法中呼叫 initInstances方法, runApp呼叫ensureInitialized方法來全部每一個Binding物件都進行初始化,初始化順序是從後往前呼叫initInstances進行初始化,同時也對Window分解的內容進行BindingBase完成過程

1.構造方法中預設呼叫initInstances
2.構造方法中預設呼叫initServiceExtensions
3.同時BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding 也和呼叫自己的上述兩個構造方法
4.完成FlutterUI層和FlutterEngine之間的互動過程
複製程式碼

flutterUI框架中提供的實現BindingBase的類

每個類的具體功能檢視一下對應類的:構造方法中預設呼叫initInstances,initServiceExtensions

Object (dart.core)
BindingBase (binding.dart) 所有Binding類的父類,之後初始化一次
SemanticsBinding (binding.dart)
PaintingBinding (binding.dart)
ServicesBinding (binding.dart)
SchedulerBinding (binding.dart)
GestureBinding (binding.dart)
RendererBinding (binding.dart)
RenderingFlutterBinding (binding.dart)
WidgetsBinding (binding.dart)  FlutterUI的統一註冊入口
WidgetsFlutterBinding (binding.dart)
TestWidgetsFlutterBinding (binding.dart)
複製程式碼

PaintingBinding

繫結畫筆庫,處理影象快取,和ServicesBinding 配套使用

mixin PaintingBinding on BindingBase, ServicesBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _imageCache = createImageCache();
    if (shaderWarmUp != null) {
      shaderWarmUp.execute();
    }
  }
複製程式碼

ServicesBinding

監聽平臺訊息並將它們定向到[BinaryMessages]。[ServicesBinding]還註冊了一個公開的[LicenseEntryCollector]在儲存在資產根目錄的LICENSE檔案中找到的許可證捆綁,並實現ext.flutter.evict服務擴充套件

mixin ServicesBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window
      ..onPlatformMessage = BinaryMessages.handlePlatformMessage;
    initLicenses();
  }
複製程式碼

SchedulerBinding

用於執行以下內容的排程程式:

  • Transient回撥,由系統的[Window.onBeginFrame]觸發

回撥,用於將應用程式的行為同步到系統顯示例如,[Ticker]和[AnimationController]的觸發器來自這些。

  • Persistent callbacks,由系統的[Window.onDrawFrame]觸發回撥,用於在瞬態回撥後更新系統的顯示執行例如,渲染層使用它來驅動它渲染管道。

    • Post-frame callbacks,僅在持久回撥之後執行

從[Window.onDrawFrame]回撥返回之前。 *非渲染任務,在幀之間執行。給出了這些 優先順序,根據a按優先順序順序執行

mixin SchedulerBinding on BindingBase, ServicesBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onBeginFrame = _handleBeginFrame;
    window.onDrawFrame = _handleDrawFrame;
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }
複製程式碼

監聽平臺訊息並將它們定向到[BinaryMessages]。[ServicesBinding]還註冊了一個公開的[LicenseEntryCollector]在儲存在資產根目錄的LICENSE檔案中找到的許可證捆綁,並實現ext.flutter.evict服務擴充套件。

mixin ServicesBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window
      ..onPlatformMessage = BinaryMessages.handlePlatformMessage;
    initLicenses();
  }
複製程式碼

GestureBinding

當[GestureBinding]收到[PointerDownEvent]時(來自[Window.onPointerDataPacket],由。解釋[PointerEventConverter]),執行[hitTest]以確定哪個[HitTestTarget]節點受到影響。 (預計其他約束力實現[hitTest]以推遲[HitTestable]物件。例如,渲染層延伸到[RenderView]和渲染物件的其餘部分層次結構。)

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
複製程式碼

RendererBinding

/// The glue between the render tree and the Flutter engine.
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    _mouseTracker = _createMouseTracker();
  }
複製程式碼

WidgetsBinding

/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }

複製程式碼

原始碼實現

使給定的小部件填充並將其附加到螢幕上。小部件在佈局期間被賦予約束,迫使它填充整個螢幕。如果您希望將視窗小部件對齊到螢幕的一側(例如,頂部),考慮使用[Align]小部件。如果你想中心您的小部件,您也可以使用[中心]小部件再次呼叫[runApp]將從螢幕上分離上一個根小部件並將給定的小部件附加到其位置。比較新的小部件樹針對上一個視窗小部件樹,任何差異都應用於底層渲染樹,類似於[StatefulWidget]時發生的情況呼叫[State.setState]後重建。如有必要,使用[WidgetsFlutterBinding]初始化繫結。 也可以看看:[WidgetsBinding.attachRootWidget],它為。建立根小部件小部件層次結構。 [RenderObjectToWidgetAdapter.attachToRenderTree],它建立了根元素層次結構的元素。 [WidgetsBinding.handleBeginFrame],它將視窗小部件管道泵送到確保構建小部件,元素和渲染樹。

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

WidgetsBinding整合Bindingbase:

1.構造方法中預設呼叫initInstances

2.構造方法中預設呼叫initServiceExtensions

3.同時BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding 也和呼叫自己的上述兩個構造方法

4.完成FlutterUI層和FlutterEngine之間的互動過程

/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }

  static WidgetsBinding get instance => _instance;
  static WidgetsBinding _instance;

  @override
  void initServiceExtensions() {
    super.initServiceExtensions();

      registerBoolServiceExtension(
        name: 'showPerformanceOverlay',
        getter: () =>
        Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
        setter: (bool value) {
          if (WidgetsApp.showPerformanceOverlayOverride == value)
            return Future<void>.value();
          WidgetsApp.showPerformanceOverlayOverride = value;
          return _forceRebuild();
        },
      );
      WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension);
      return true;
    }());
  }

複製程式碼

FlutterUI和FlutterEngine之間的粘合劑BindingBase

1.BindingBase初始化

用於提供單例服務的mixins的基類(也稱為“Binding”)。在mixin的on子句中使用這個類,繼承它並實現[initInstances()]。 mixin保證只能構建一次應用程式的生命週期(更確切地說,如果構造兩次,它將斷言 在檢查模式下)。用於編寫應用程式的最頂層將具有一個具體的類繼承自[BindingBase]並使用所有各種[BindingBase]mixins(例如[ServicesBinding])。例如,Widgets庫中Flutter引入了一個名為[WidgetsFlutterBinding]的繫結。相關的library定義瞭如何建立繫結。可以暗示(例如,[WidgetsFlutterBinding]自動從[runApp]開始,或者應用程式可能需要顯式呼叫建構函式。小部件層和Flutter引擎之間的粘合劑。 初始化繫結時呼叫,註冊服務擴充套件。想要公開服務擴充套件的繫結應該過載這個方法使用呼叫來註冊它們 [registerSignalServiceExtension],[registerBoolServiceExtension], [registerNumericServiceExtension],和 [registerServiceExtension](按複雜程度遞增)。此方法的實現必須呼叫它們的超類實施{@macro flutter.foundation.bindingBase.registerServiceExtension} 也可以看看:

2.和Window類的關聯

繫結此繫結的視窗。許多其他繫結被定義為[BindingBase]的擴充套件,,例如,[ServicesBinding],[RendererBinding]和[WidgetsBinding]。每個這些繫結定義了與[ui.Window]互動的行為,例如,[ServicesBinding]註冊一個[ui.Window.onPlatformMessage]處理程式,和[RendererBinding]註冊[ui.Window.onMetricsChanged],[ui.Window.onTextScaleFactorChanged],[ui.Window.onSemanticsEnabledChanged],和[ui.Window.onSemanticsAction]處理程式。這些其他繫結中的每一個都可以靜態地單獨訪問[Window],但這會妨礙用假冒測試這些行為的能力視窗用於驗證目的。因此,[BindingBase]暴露了這一點[Window]供其他繫結使用。 [BindingBase]的子類,如 [TestWidgetsFlutterBinding],可以覆蓋此訪問器以返回a不同的[Window]實現,例如[TestWindow]。

abstract class BindingBase {
  BindingBase() {
    1.初始化例項本身
    2.註冊擴充套件服務
    .......
    initInstances();
    initServiceExtensions();
    .......
  }
  獲取window類
  ui.Window get window => ui.window;

  void initInstances() {
  }
  註冊擴充套件服務
  @protected
  @mustCallSuper
  void initServiceExtensions() {

    assert(() {
      const String platformOverrideExtensionName = 'platformOverride';
      registerServiceExtension(
        name: platformOverrideExtensionName,
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
            switch (parameters['value']) {
              case 'android':
                debugDefaultTargetPlatformOverride = TargetPlatform.android;
                break;
              case 'iOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
                break;
              case 'fuchsia':
                debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
                break;
              case 'default':
              default:
                debugDefaultTargetPlatformOverride = null;
            }
            _postExtensionStateChangedEvent(
              platformOverrideExtensionName,
              defaultTargetPlatform.toString().substring('$TargetPlatform.'.length),
            );
            await reassembleApplication();
          }
          return <String, dynamic>{
            'value': defaultTargetPlatform
                     .toString()
                     .substring('$TargetPlatform.'.length),
          };
        },
      );
      return true;
    }());
  }
。。。。。。。。。。。。
}

複製程式碼

window

Flutter使用者UI層是怎麼和Window關聯起來

上一節中,已經把系統為我們建立好的Binding類,定義了多個,主要目的就是分別對FlutterEngine和FlutterUI直接的回撥和互動做區分處理,達到程式碼解耦的目的

主機作業系統使用者介面的最基本介面。系統中有一個Window例項,您可以這樣做從[window]屬性獲取 FlutterUI使用者是怎麼訪問Window介面的?先使用一個極簡的案例來展示一個FlutterUI專案呼叫FlutterEngine中的介面

Window是如何最簡單的一幀繪製在螢幕上的

使用新提供的更新應用程式在GPU上的渲染[Scene]必須在範圍內呼叫此函式[onBeginFrame]或[onDrawFrame]回撥被呼叫。如果這個功能在單個[onBeginFrame] / [onDrawFrame]中第二次被呼叫回撥序列或呼叫那些回撥範圍之外的呼叫將被忽略。要記錄圖形操作,首先要建立一個[PictureRecorder]構造一個[Canvas],將[PictureRecorder]傳遞給它的建構函式。發出所有圖形操作後,呼叫[PictureRecorder.endRecording]函式在[PictureRecorder]上獲取表示已釋出圖形操作的最終[Picture]。接下來,建立一個[SceneBuilder],然後使用新增[Picture][SceneBuilder.addPicture]。使用[SceneBuilder.build]方法即可然後獲取[Scene]物件,您可以通過此物件顯示該物件[渲染]功能。也可以看看:[SchedulerBinding],用於管理的Flutter框架類幀的排程。[RendererBinding],Flutter框架類,用於管理佈局和畫。

flutterwindow.png

Flutter UI架構啟動過程-11

核心實現:

color = const ui.Color(0xFF00FF00);
提供一個Frame資料給Window
ui.window.onBeginFrame = beginFrame;
處理點選事件
ui.window.onPointerDataPacket = handlePointerDataPacket;
呼叫scheduleFrame方法呼叫native方法訪問Flutterengine
ui.window.scheduleFrame();
複製程式碼

需要彙總的物件新增到window.render中 final ui.SceneBuilder sceneBuilder = ui.SceneBuilder() // TODO(abarth): We should be able to add a picture without pushing a // container layer first. ..pushClipRect(physicalBounds) ..addPicture(ui.Offset.zero, picture) ..pop();

ui.window.render(sceneBuilder.build());
複製程式碼
import 'dart:ui' as ui;

void beginFrame(Duration timeStamp) {
  final double devicePixelRatio = ui.window.devicePixelRatio;
  final ui.Size logicalSize = ui.window.physicalSize / devicePixelRatio;

  final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
    ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
  )
    ..addText('Hello, world.');
  final ui.Paragraph paragraph = paragraphBuilder.build()
    ..layout(ui.ParagraphConstraints(width: logicalSize.width));

  final ui.Rect physicalBounds = ui.Offset.zero & (logicalSize * devicePixelRatio);
  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder, physicalBounds);
  canvas.scale(devicePixelRatio, devicePixelRatio);
  canvas.drawParagraph(paragraph, ui.Offset(
    (logicalSize.width - paragraph.maxIntrinsicWidth) / 2.0,
    (logicalSize.height - paragraph.height) / 2.0,
  ));
  final ui.Picture picture = recorder.endRecording();

  final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
    ..pushClipRect(physicalBounds)
    ..addPicture(ui.Offset.zero, picture)
    ..pop();

  ui.window.render(sceneBuilder.build());
}

void main() {
  ui.window.onBeginFrame = beginFrame;
  ui.window.scheduleFrame();
}

複製程式碼

總結

通過上面的分析,已經對Window相關的介面繼續解耦到不同的binding物件,所有的初始化操作都是在initInstances()方法中進行初始化的

1.呼叫runApp開始啟動一個AppWidget層的入口

2.呼叫WidgetsFlutterBindingensureInitialized方法來對BindingBase的所有子類進行初始化

3.mixin類的初始化是從繼承順序從後往前進行初始化

通過上面的分析和demo的演示,我們可以很方便的瞭解Widow事件的拆分,不同BindingBase的初始化、以及Window的渲染物件回撥到Flutterengin層提供渲染物件,後續的文章將介紹,FlutterWidget層是如何返回Flutter渲染物件的,FlutterUI的核心功能就是把使用者介面最終合併到一幀上,其中包括 1.視窗管理 2.平臺訊息管理 3.使用者介面管理 4.導航 5.觸控,和點選事件的處理邏輯 更加詳細的邏輯,在後面的檔案中在做框架性的解析,先把核心的邏輯搞得,啟動的Flutter細節的框架知識,在做彙總學習,最終目的是使用,FlutterUI相關的文件和齊全,在編寫程式碼是根據不同的框架在SDK中都會有相關的例項程式碼,編寫程式碼儘量有自己的梯子youtub上面有大量的教程,避免在細緻末節上上花太多的時間,先把導航調好,在開車上路,大方向不好有錯,提高執行效率,就可以把事件解決了-------個人的學習和工作的方法論,希望大家可以一起探討