FlutterSwift

FlutterSwift 旨在帮助您使用 Dart 编写 UI,并使用 Swift 编写业务逻辑。

它由三个组件构成

最终目标是允许以跨平台的方式使用 Flutter 进行 UI,并使用 Swift 进行业务逻辑。目前支持的目标平台包括 macOS、iOS、Android 和 eLinux。

以下内容假定您对 Flutter(特别是平台通道)和 Swift 语言都有相当程度的熟悉。

架构

移动和桌面平台

在移动和桌面平台(如 macOS、iOS 和 Android)上,FlutterPlatformMessenger 类包装了平台现有的 二进制消息传递器。这是因为平台二进制消息传递器是不可替换的,因为它被宿主平台插件使用。

在 Darwin 平台(即 iOS 和 macOS)上,您只需从 Xcode 将 FlutterSwift 添加为 Swift 包依赖项即可。在 Android 上,您需要将 FlutterSwift 链接到一个 Java 本地接口 (JNI) 库中,该库与您的 APK 捆绑在一起(更多内容见下文)。

嵌入式平台

FlutterDesktopMessenger Actor 包装了 flutter_messenger.h 中的 API。此软件包将构建 Sony eLinux Flutter 分支作为子模块,使用此存储库中的工件包中包含的 Flutter 引擎。

请注意 Flutter *嵌入* 或 *嵌入器* 之间的区别,它们是 Flutter 框架与应用程序的平台特定集成,以及 *嵌入式* 用例。

示例

iOS 和 macOS

示例 Xcode 项目包含在 Examples/counter 目录的标准位置。您可能需要调整 bundle identifier 以匹配您的开发者 ID。

Android

Android 构建目前仅在 macOS 上受支持,并且需要安装以下依赖项

然后,您需要编辑 build-android.sh 脚本,并在必要时更改以下环境变量

示例的 Android 特定源代码位于 Examples/counter/android/app/src/main

这里工具的某些不便之处是已知问题,我们计划在未来 改进它

请注意,@MainActor 在 Android 上不可用;请改用 @UIThreadActor

嵌入式 Linux

假设 Flutter SDK 安装在 /opt/flutter-elinux/flutter 中,您只需在顶层目录中运行 ./build-counter-linux.sh,然后运行 ./run-counter-linux.sh 即可。这将构建 Flutter AOT 对象,然后构建 Swift 运行器。

环境变量 FLUTTER_SWIFT_BACKEND 可以设置为 gbmeglstreamwayland 中的一个,具体取决于情况。这应该在构建和运行时都设置。除非您真正在嵌入式系统上进行测试,否则您可能希望将其设置为 wayland

用法

本节简要概述 FlutterSwift 提供的 API。

初始化

macOS

import FlutterMacOS.FlutterBinaryMessenger
import FlutterSwift

override func awakeFromNib() {
  let flutterViewController = FlutterViewController() // from platform embedding
  let binaryMessenger = FlutterSwift
        .FlutterPlatformMessenger(wrapping: flutterViewController.engine.binaryMessenger)
  ...
}

Android

Android 要求您的应用程序的 configureFlutterEngine() 方法调用您定义的本地函数来初始化您的平台通道,例如以下内容

package com.example.counter;

import io.flutter.plugin.common.BinaryMessenger;

public final class ChannelManager {
  public final BinaryMessenger binaryMessenger;

  public ChannelManager(BinaryMessenger binaryMessenger) {
    System.loadLibrary("counter");
    this.binaryMessenger = binaryMessenger;
  }

  public native void initChannelManager();
}

在您的 Swift 代码(此处为 initChannelManager())中,您可以注册您的平台通道实现

import FlutterAndroid
import JavaKit
import JavaRuntime

@JavaClass("com.example.counter.ChannelManager")
open class _ChannelManager: JavaObject {
  @JavaField(isFinal: true)
  public var binaryMessenger: FlutterAndroid.FlutterBinaryMessenger!

  @JavaMethod
  @_nonoverride
  public convenience init(
    _ binaryMessenger: FlutterAndroid.FlutterBinaryMessenger?,
    environment: JNIEnvironment? = nil
  )
}

protocol _ChannelManagerNativeMethods {
  func initChannelManager()
}

@JavaImplementation("com.example.counter.ChannelManager")
extension _ChannelManager: _ChannelManagerNativeMethods {
  @JavaMethod
  public func initChannelManager() {
    let wrappedMessenger = FlutterPlatformMessenger(wrapping: binaryMessenger!)
    // initialize your channels, remembering to take a strong reference to them
  }
}

eLinux

在 Linux 上,使用原生 Swift 客户端包装器

@main
enum SomeApp {
  static func main() {
    guard CommandLine.arguments.count > 1 else {
      print("usage: SomeApp [flutter_path]")
      exit(1)
    }
    let dartProject = DartProject(path: CommandLine.arguments[1])
    let viewProperties = FlutterViewController.ViewProperties(
            width: 640,
            height: 480,
            title: "SomeApp",
            appId: "com.example.SomeApp"
    )
    let window = FlutterWindow(properties: viewProperties, project: dartProject)
    guard let window else {
      debugPrint("failed to initialize window!")
      exit(2)
    }
    let binaryMessenger = viewController.engine.binaryMessenger
    ...
    window.run()
  }
}

通道

消息通道

这展示了一个使用 JSON 消息编解码器的基本消息通道处理程序。在 eLinux 上,不要在 awakeFromNib() 中注册通道,而是从 main() 函数调用此方法(可能由管理器类间接调用)。

private func messageHandler(_ arguments: String?) async -> Int? {
  debugPrint("Received message \(String(describing: arguments))")
  return 0xCAFE_BABE
}

override func awakeFromNib() {
...
  flutterBasicMessageChannel = FlutterBasicMessageChannel(
    name: "com.example.SomeApp.basic",
    binaryMessenger: binaryMessenger,
    codec: FlutterJSONMessageCodec.shared
  )

  task = Task {
    try! await flutterBasicMessageChannel.setMessageHandler(messageHandler)
    ...
  }
}

方法通道

var isRunning = true

private func methodCallHandler(
  call: FlutterSwift.FlutterMethodCall<Bool>
) async throws -> Bool {
  isRunning.toggle()
  return isRunning
}

override func awakeFromNib() {
...
  let flutterMethodChannel = FlutterMethodChannel(
    name: "com.example.SomeApp.toggle",
        binaryMessenger: binaryMessenger
    )
    task = Task {
      try! await flutterMethodChannel.setMethodCallHandler(methodCallHandler)
  }
}

事件通道

这是一个事件通道的示例,摘自 counter 示例。

import AsyncAlgorithms
import AsyncExtensions
import FlutterSwift
...

typealias Arguments = FlutterNull
typealias Event = Int32
typealias Stream = AsyncThrowingChannel<Event?, FlutterError>

var flutterEventStream = Stream()
var task: Task<(), Error>?
var counter: Event = 0

private func onListen(_ arguments: Arguments?) throws -> FlutterEventStream<Event> {
  flutterEventStream.eraseToAnyAsyncSequence()
}

private func onCancel(_ arguments: Arguments?) throws {
  task?.cancel()
  task = nil
}

override func awakeFromNib() {
...
  let flutterEventChannel = FlutterEventChannel(
    name: "com.example.SomeApp.counterEvents",
    binaryMessenger: binaryMessenger
  )
  task = Task {
    try! await flutterEventChannel.setStreamHandler(onListen: onListen, onCancel: onCancel)
    repeat {
      await flutterEventStream.send(counter)
      count += 1
      try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    } while !Task.isCancelled
  }
}