天天看點

flutter架構(4):平台嵌入混編Platform embedding --平台嵌入混編

flutter架構(1):概述

flutter架構(2):Widget

flutter架構(3):渲染和布局

flutter架構(5):web支援

文章目錄

  • Platform embedding --平台嵌入混編
    • Integrating with other code --與其他代碼內建
    • Platform channels
    • Foreign Function Interface --外圍功能功能
    • Rendering native controls in a Flutter app --在Flutter應用中渲染native控件
    • Hosting Flutter content in a parent app --在現有app中接入Flutter

Platform embedding --平台嵌入混編

As we’ve seen, rather than being translated into the equivalent OS widgets, Flutter user interfaces are built, laid out, composited, and painted by Flutter itself. The mechanism for obtaining the texture and participating in the app lifecycle of the underlying operating system inevitably varies depending on the unique concerns of that platform. The engine is platform-agnostic, presenting a stable ABI (Application Binary Interface) that provides a platform embedder with a way to set up and use Flutter.

如我們所見,Flutter使用者界面不是轉換成等效的OS widget, 而是由Flutter自身build,layout,組合和paint。 “擷取紋理”并且參“與基礎作業系統的app生命周期”的機制不可避免地會根據該平台的特性而變化。 Flutter引擎做到平台無關,它提供了一個平台embedder程式–穩定ABI(app二進制接口),通過ABI,平台可以設定和使用Flutter。

The platform embedder is the native OS application that hosts all Flutter content, and acts as the glue between the host operating system and Flutter. When you start a Flutter app, the embedder provides the entrypoint, initializes the Flutter engine, obtains threads for UI and rastering, and creates a texture that Flutter can write to. The embedder is also responsible for the app lifecycle, including input gestures (such as mouse, keyboard, touch), window sizing, thread management, and platform messages. Flutter includes platform embedders for Android, iOS, Windows, macOS, and Linux; you can also create a custom platform embedder, as in this worked example that supports remoting Flutter sessions through a VNC-style framebuffer or this worked example for Raspberry Pi.

平台embedder是承載所有Flutter内容的native OS app,并且充當主機作業系統和Flutter之間的粘合劑。 啟動Flutter app時,embedder程式将提供入口點,初始化Flutter引擎,擷取用于UI和栅格化的線程,并建立Flutter可以寫入的紋理。 embedder還負責應用程式的生命周期,包括輸入手勢(例如滑鼠,鍵盤,觸摸),視窗大小,線程管理和平台消息。 Flutter包含适配embedder了的平台有:Android,iOS,Windows,macOS和Linux等; 你還可以建立一個自定義平台embedder程式,如this worked example 中所示,它支援通過VNC樣式的幀緩沖區或this worked example for Raspberry Pi。

Each platform has its own set of APIs and constraints. Some brief platform-specific notes:

每個平台都有自己的一組API和限制。 一些特定于平台的簡短說明:

  1. On iOS and macOS, Flutter is loaded into the embedder as a UIViewController or NSViewController, respectively. The platform embedder creates a FlutterEngine, which serves as a host to the Dart VM and your Flutter runtime, and a FlutterViewController, which attaches to the FlutterEngine to pass UIKit or Cocoa input events into Flutter and to display frames rendered by the FlutterEngine using Metal or OpenGL.

    在iOS和macOS上,Flutter分别作為UIViewController或NSViewController加載到embedder中。 平台embedder程式建立一個FlutterEngine(作為Dart VM和你的Flutter運作時的主機),以及一個FlutterViewController(attache到FlutterEngine),以将UIKit或Cocoa輸入事件傳遞到Flutter,FlutterEngine使用Metal或OpenGL顯示渲染的幀。

  2. On Android, Flutter is, by default, loaded into the embedder as an Activity. The view is controlled by a FlutterView, which renders Flutter content either as a view or a texture, depending on the composition and z-ordering requirements of the Flutter content.

    在Android上,預設情況下,Flutter作為Activity加載到embedder中。 View由FlutterView進行控制,FlutterView将Flutter内容呈現為view或紋理(texture),具體取決于Flutter内容的組合和z-ordering。

  3. On Windows, Flutter is hosted in a traditional Win32 app, and content is rendered using ANGLE, a library that translates OpenGL API calls to the DirectX 11 equivalents. Efforts are currently underway to also offer a Windows embedder using the UWP app model, as well as to replace ANGLE with a more direct path to the GPU via DirectX 12.

    在Windows上,Flutter托管在傳統的Win32應用程式中,并且使用ANGLE(一個将OpenGL API調用轉換為DirectX 11等效項的庫)呈現内容。 目前正在努力使用UWP應用程式模型提供Windows embedder程式,并通過DirectX 12以更直接的方式将ANGLE替換為GPU。

Integrating with other code --與其他代碼內建

Flutter provides a variety of interoperability mechanisms, whether you’re accessing code or APIs written in a language like Kotlin or Swift, calling a native C-based API, embedding native controls in a Flutter app, or embedding Flutter in an existing application.

Flutter提供了多種互操作性機制,無論是通路以Kotlin或Swift之類的語言編寫的代碼或API,調用基于C的 native API,将native控件嵌入Flutter應用程式中,還是将Flutter嵌入現有應用程式中。

Platform channels

For mobile and desktop apps, Flutter allows you to call into custom code through a platform channel, which is a simple mechanism for communicating between your Dart code and the platform-specific code of your host app. By creating a common channel (encapsulating a name and a codec), you can send and receive messages between Dart and a platform component written in a language like Kotlin or Swift. Data is serialized from a Dart type like Map into a standard format, and then deserialized into an equivalent representation in Kotlin (such as HashMap) or Swift (such as Dictionary).

對于移動和桌面app,Flutter允許你通過 platform channel調用自定義代碼,這是在Dart代碼與宿主app的平台特定代碼之間進行通信的一種簡單機制。 通過建立公共channel(封裝名稱和編解碼器),您可以在Dart與以Kotlin或Swift之類的語言編寫的平台元件之間發送和接收消息。 資料從諸如Map的Dart類型序列化為标準格式,然後反序列化為Kotlin(例如HashMap)或Swift(例如Dictionary)的等效表示形式。

flutter架構(4):平台嵌入混編Platform embedding --平台嵌入混編

The following is a simple platform channel example of a Dart call to a receiving event handler in Kotlin (Android) or Swift (iOS):

以下是簡單platform channel 示例:在Kotlin(Android)或Swift(iOS)中Dart調用接收事件進行處理:

// Dart side
const channel = MethodChannel('foo');
final String greeting = await channel.invokeMethod('bar', 'world');
print(greeting);
           
// Android (Kotlin)
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
  when (call.method) {
    "bar" -> result.success("Hello, ${call.arguments}")
    else -> result.notImplemented()
  }
}
           
// iOS (Swift)
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
  (call: FlutterMethodCall, result: FlutterResult) -> Void in
  switch (call.method) {
    case "bar": result("Hello, \(call.arguments as! String)")
    default: result(FlutterMethodNotImplemented)
  }
}
           

Further examples of using platform channels, including examples for macOS, can be found in the flutter/plugins repository. There are also thousands of plugins already available for Flutter that cover many common scenarios, ranging from Firebase to ads to device hardware like camera and Bluetooth.

可以在 flutter/plugins repository中找到使用platform channel的更多示例,包括macOS的示例。 Flutter也有 成千上萬的插件可用 ,裡面涵蓋了許多常見的場景,從Firebase到廣告再到相機和藍牙等裝置硬體。

Foreign Function Interface --外圍功能功能

For C-based APIs, including those that can be generated for code written in modern languages like Rust or Go, Dart provides a direct mechanism for binding to native code using the dart:ffi library. The foreign function interface (FFI) model can be considerably faster than platform channels, because no serialization is required to pass data. Instead, the Dart runtime provides the ability to allocate memory on the heap that is backed by a Dart object and make calls to statically or dynamically linked libraries. FFI is available for all platforms other than web, where the js package serves an equivalent purpose.

對于基于C的API,包括用Rust或Go等現代語言編寫的代碼生成的API,Dart提供了一種直接的機制,用于使用dart:ffi 庫綁定到本機代碼。 外圍功能功能(FFI)模型可以比platform channels快得多,因為不需要序列化即可傳遞資料。 相反,Dart runtime提供了在Dart對象支援的堆上配置設定記憶體并調用靜态或動态連結庫的功能。 FFI适用于除Web之外的所有平台,Web的 js package 具有相同的用途。

To use FFI, you create a typedef for each of the Dart and unmanaged method signatures, and instruct the Dart VM to map between them. As a simple example, here’s a fragment of code to call the traditional Win32 MessageBox() API:

要使用FFI,請為每個Dart和非托管方法簽名建立一個typedef,并訓示Dart VM在它們之間進行映射。 舉一個簡單的例子,下面是一段代碼片段,用于調用傳統的Win32 MessageBox() API:

typedef MessageBoxNative = Int32 Function(
    IntPtr hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, Int32 uType);
typedef MessageBoxDart = int Function(
    int hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, int uType);

final user32 = DynamicLibrary.open('user32.dll');
final MessageBox =
    user32.lookupFunction<MessageBoxNative, MessageBoxDart>('MessageBoxW');

final result = MessageBox(
    0, // No owner window
    Utf16.toUtf16('Test message'),   // Message
    Utf16.toUtf16('Window caption'), // Window title
    0 // OK button only
    );
           

Rendering native controls in a Flutter app --在Flutter應用中渲染native控件

Because Flutter content is drawn to a texture and its widget tree is entirely internal, there’s no place for something like an Android view to exist within Flutter’s internal model or render interleaved within Flutter widgets. That’s a problem for developers that would like to include existing platform components in their Flutter apps, such as a browser control.

由于Flutter的内容被繪制到紋理上,并且它的widget樹完全在内部,是以在Flutter的内部模型中無法存在Android View之類的内容,而在Flutter的widget中則無法交錯顯示。 對于想要在Flutter應用程式中包含現有平台元件(例如浏覽器控件)的開發人員來說,這是一個問題。

Flutter solves this by introducing platform view widgets (AndroidView and UiKitView) that let you embed this kind of content on each platform. Platform views can be integrated with other Flutter content. Each of these widgets acts as an intermediary to the underlying operating system. For example, on Android,AndroidView serves three primary functions:

Flutter通過引入platform view widget(AndroidView和UiKitView)解決了這一問題,您可以在每個平台上混編這類内容(附:這種方法有一些局限性,例如,透明度對于platform view的組合方式與其他Flutter widget的組合方式不同。)。 platform view 可以與其他Flutter内容內建。 這些widget中的每一個都充當基礎作業系統的中介橋梁。 例如,在Android上,AndroidView具有三個主要功能:

  1. Making a copy of the graphics texture rendered by the native view and presenting it to Flutter for composition as part of a Flutter-rendered surface each time the frame is painted.

    複制native view渲染的圖形紋理,并在繪制每一幀時将其呈現給Flutter,以作為Flutter渲染surface中的一部分。

  2. Responding to hit testing and input gestures, and translating those into the equivalent native input.

    響應hit test和輸入手勢,并将其轉換為等效的native輸入。

  3. Creating an analog of the accessibility tree, and passing commands and responses between the native and Flutter layers.

    Inevitably, there is a certain amount of overhead associated with this synchronization. In general, therefore, this approach is best suited for complex controls like Google Maps where reimplementing in Flutter isn’t practical.

    建立類似的可通路性樹,并在native層和Flutter層之間傳遞指令和響應。

    不可避免地,與此同步相關聯有一定的開銷成本。 是以,通常,這種方法最适合于複雜的控件,例如Google地圖,這種在Flutter中不可能重新實作一套。

Typically, a Flutter app instantiates these widgets in a build() method based on a platform test. As an example, from the google_maps_flutter plugin:

通常,Flutter app會基于平台測試在build()方法中執行個體化這些小部件。 例如,通過google_maps_flutter 插件:

if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'plugins.flutter.io/google_maps',
        onPlatformViewCreated: onPlatformViewCreated,
        gestureRecognizers: gestureRecognizers,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      return UiKitView(
        viewType: 'plugins.flutter.io/google_maps',
        onPlatformViewCreated: onPlatformViewCreated,
        gestureRecognizers: gestureRecognizers,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    }
    return Text(
        '$defaultTargetPlatform is not yet supported by the maps plugin');
  }

           

Communicating with the native code underlying the AndroidView or UiKitView typically occurs using the platform channels mechanism, as previously described.

如前所述,通常使用platform channel機制與AndroidView或UiKitView基礎的native代碼進行通信。

At present, platform views aren’t available for desktop platforms, but this is not an architectural limitation; support might be added in the future.

目前,platform view不适用于桌面平台,但這不是體系結構上的限制; 将來可能會增加支援。

Hosting Flutter content in a parent app --在現有app中接入Flutter

The converse of the preceding scenario is embedding a Flutter widget in an existing Android or iOS app. As described in an earlier section, a newly created Flutter app running on a mobile device is hosted in an Android activity or iOS UIViewController. Flutter content can be embedded into an existing Android or iOS app using the same embedding API.

與上述場景相反的是将Flutter widget 混編嵌入現有的Android或iOS app中。 如前一節所述,在移動裝置上運作的新建立的Flutter app托管在Android Activity或iOS UIViewController中。 可以使用相同的嵌入API将Flutter内容嵌入到現有的Android或iOS應用中。

The Flutter module template is designed for easy embedding; you can either embed it as a source dependency into an existing Gradle or Xcode build definition, or you can compile it into an Android Archive or iOS Framework binary for use without requiring every developer to have Flutter installed.

Flutter module模闆設計成易于嵌入; 你可以将其作為源依賴項嵌入到現有的Gradle或Xcode build definition中,也可以将其編譯為Android Archive或iOS Framework二進制檔案以供使用,而無需每個開發人員都安裝Flutter。

The Flutter engine takes a short while to initialize, because it needs to load Flutter shared libraries, initialize the Dart runtime, create and run a Dart isolate, and attach a rendering surface to the UI. To minimize any UI delays when presenting Flutter content, it’s best to initialize the Flutter engine during the overall app initialization sequence, or at least ahead of the first Flutter screen, so that users don’t experience a sudden pause while the first Flutter code is loaded. In addition, separating the Flutter engine allows it to be reused across multiple Flutter screens and share the memory overhead involved with loading the necessary libraries.

Flutter引擎需要很短的時間來初始化,因為它需要加載Flutter共享庫,初始化Dart runtime,建立并運作Dart isolate,并将渲染圖面attach到UI。 為了最大程度地減少顯示Flutter内容時的UI延遲,最好在整個app初始化過程中或至少在第一個Flutter螢幕之前初始化Flutter engine,以使使用者在載入第一個Flutter代碼時不會突然停頓。 此外,将Flutter engine分開可以使其在多個Flutter螢幕上重複使用,并共享與加載必要庫有關的記憶體開銷。

More information about how Flutter is loaded into an existing Android or iOS app can be found at the Load sequence, performance and memory topic.

有關如何将Flutter加載到現有Android或iOS應用程式的更多資訊,請參見Flutter的加載流程,性能,記憶體情況。