Flutter 与原生混合开发是一个非常重要且实用的主题。它允许团队在不完全重写现有应用的情况下,逐步引入 Flutter,或者在 Flutter 应用中利用强大的原生生态。

Flutter 混合开发主要有两种模式:

  1. 将 Flutter 集成到现有原生应用 (Add-to-App):这是最常见的混合模式。你有一个成熟的 Android 或 iOS 应用,希望用 Flutter 来开发新的功能模块、页面,甚至是重构部分旧页面。

  2. 在 Flutter 应用中嵌入原生视图 (Platform Views):你的应用主体是 Flutter,但需要嵌入一个无法或很难用 Flutter 实现的原生组件,例如 Google Maps、特定的广告 SDK 视图、或者一个复杂的原生图表库。

下面我们详细探讨这两种模式。


模式一:将 Flutter 集成到现有原生应用 (Add-to-App)

这是 Flutter 官方重点支持的方案。原生应用作为“宿主”,Flutter 模块作为“客人”被嵌入其中。

核心概念:FlutterEngine

FlutterEngine 是 Flutter 运行的核心。它包含了 Dart VM、你的 Dart 代码、以及 Flutter 的所有运行时服务。管理 FlutterEngine 的生命周期是混合开发性能的关键

  • 冷启动 (Cold Start):每次进入 Flutter 页面时都创建一个新的 FlutterEngine。这会导致明显的延迟和白屏,因为引擎需要初始化、加载 Dart 代码并渲染第一帧。应尽量避免

  • 热启动 (Warm Start):提前创建并“预热”一个 FlutterEngine 实例,并将其缓存起来。当需要展示 Flutter 页面时,直接使用这个缓存的引擎。这能极大提升 Flutter 页面的打开速度,带来近乎原生的体验。

实现流程
1. 创建 Flutter Module

首先,你需要创建一个特殊的 Flutter 项目,它不是一个完整的 App,而是一个可以被原生项目依赖的模块。

flutter create --template module my_flutter_module

这个命令会创建一个 my_flutter_module 目录,其中包含了 Flutter 代码,以及一个隐藏的 .android 和 .ios 目录,它们包含了将 Flutter 模块打包成原生库(AAR/Framework)的脚本。

2. 原生项目集成

在 Android 中 (Kotlin/Java):

  1. 依赖 Flutter 模块: 在原生项目的 settings.gradle 中,添加对 Flutter 模块的引用。Gradle 会自动处理编译和打包。

  2. 预热 FlutterEngine: 在你的 Application 类中,创建并缓存一个 FlutterEngine 实例。

```kotlin
class MyApplication : Application() {
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()
        // 实例化 FlutterEngine
        flutterEngine = FlutterEngine(this)

        // 预热引擎,执行 Dart 代码的 main() 函数
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // 缓存引擎以供复用
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}
```
  1. 启动 Flutter 页面: 从你的原生 Activity 中,使用缓存的引擎启动 FlutterActivity

```kotlin
// 在原生 Button 的点击事件中
button.setOnClickListener {
    // 使用 withCachedEngine 来获取预热好的引擎
    startActivity(
        FlutterActivity
            .withCachedEngine("my_engine_id")
            .build(this)
    )
}
```

在 iOS 中 (Swift):

  1. 依赖 Flutter 模块: 使用 CocoaPods 将 Flutter 模块集成到你的 iOS 项目中。在 Podfile 中添加相关脚本。

  2. 预热 FlutterEngine: 在你的 AppDelegate.swift 中,创建并缓存一个 FlutterEngine

```swift
import Flutter
import FlutterPluginRegistrant

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    lazy var flutterEngine = FlutterEngine(name: "my_engine_id")

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预热引擎
        flutterEngine.run()
        // 注册插件
        GeneratedPluginRegistrant.register(with: self.flutterEngine)
        return true
    }
}
```
  1. 展示 Flutter 页面: 从你的原生 ViewController 中,创建一个 FlutterViewController 并展示它。

```swift
// 在原生 Button 的点击事件中
@IBAction func showFlutter() {
    // 获取 AppDelegate 中的引擎实例
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let flutterEngine = appDelegate.flutterEngine
    let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    
    // 可以通过 present 或 push 的方式展示
    present(flutterViewController, animated: true, completion: nil)
}
```
3. 双向通信

原生和 Flutter 模块之间需要频繁通信,例如传递初始化数据(用户 Token、页面参数)、同步状态、回传结果等。这完全依赖于我们之前讨论的平台通道 (Platform Channels)

  • MethodChannel 是最常用的方式。例如,原生部分可以在启动 Flutter 页面前,通过 MethodChannel 告诉 Flutter 当前的用户信息。Flutter 页面关闭前,可以通过 MethodChannel 将操作结果返回给原生。

  • Pigeon 在混合开发中尤其推荐,因为它可以保证原生和 Flutter 两端数据模型的类型安全,减少因手写通道代码而出错的概率。


模式二:在 Flutter 应用中嵌入原生视图 (Platform Views)

当你的 App 主体是 Flutter,但需要嵌入一个原生视图时,使用此模式。

核心概念:AndroidView 和 UiKitView

Flutter 提供了这两个 Widget,它们可以在 Flutter 的 Widget 树中开辟一块“区域”,专门用来渲染一个原生的 View/UIView。

实现原理
  1. Flutter 端: 在你的 Widget 树中放置 AndroidView 或 UiKitView。你需要提供一个唯一的 viewType 字符串,用于标识你想创建哪种原生视图。

  2. 原生端:

    • 你需要创建一个工厂类 (PlatformViewFactory)

    • 这个工厂类注册到 Flutter 插件系统中,并与 Flutter 端的 viewType 关联起来。

    • 当 Flutter 渲染到 AndroidView/UiKitView 时,它会通知原生端,原生端通过工厂类创建一个对应的原生 View 实例。

    • Flutter 会将这个原生 View 合成到最终的屏幕画面中。

示例:在 Flutter 中嵌入一个原生的 TextView (Android)

1. Flutter 端 (Dart):

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

Widget build(BuildContext context) {
  const String viewType = '<platform-view-type>';
  final Map<String, dynamic> creationParams = <String, dynamic>{};

  if (defaultTargetPlatform == TargetPlatform.android) {
    return AndroidView(
      viewType: viewType,
      layoutDirection: TextDirection.ltr,
      creationParams: creationParams,
      creationParamsCodec: const StandardMessageCodec(),
    );
  } else if (defaultTargetPlatform == TargetPlatform.iOS) {
    return UiKitView(
      viewType: viewType,
      layoutDirection: TextDirection.ltr,
      creationParams: creationParams,
      creationParamsCodec: const StandardMessageCodec(),
    );
  }
  return Text('$defaultTargetPlatform is not yet supported by this plugin.');
}

2. Android 端 (Kotlin):

首先,创建一个 PlatformViewFactory

// NativeTextViewFactory.kt
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

class NativeTextViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map<String?, Any?>?
        return NativeTextView(context, viewId, creationParams)
    }
}

internal class NativeTextView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
    private val textView: TextView

    override fun getView(): View {
        return textView
    }

    override fun dispose() {}

    init {
        textView = TextView(context)
        textView.textSize = 24f
        textView.setBackgroundColor(Color.rgb(200, 200, 200))
        textView.text = "Rendered from Android native code"
    }
}

然后,在你的插件注册类中注册这个工厂:

// YourPlugin.kt (implements FlutterPlugin)
override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    binding
        .platformViewRegistry
        .registerViewFactory("<platform-view-type>", NativeTextViewFactory())
}
性能和注意事项
  • 性能开销: Platform Views 会带来一定的性能开销,因为它们打破了 Flutter 的统一渲染模式,需要进行额外的视图合成。在列表或动画中大量使用可能会导致掉帧。

  • 触摸事件: 触摸事件的传递需要从 Flutter 转发到原生视图,可能会有轻微延迟。

  • Android 上的合成模式:

    • Hybrid Composition (混合合成): 默认模式,性能更好,但要求 Android API 19+。

    • Virtual Display (虚拟显示): 旧模式,兼容性更好,但性能较差,且对触摸事件和可访问性支持有限。


总结对比

特性 将 Flutter 集成到原生应用 (Add-to-App) 在 Flutter 中嵌入原生视图 (Platform Views)
主导方 原生 App 是主导,Flutter 是一个或多个页面/模块。 Flutter App 是主导,原生视图是嵌入的组件。
适用场景 渐进式重构、为现有大型应用添加新功能。 复用已有的复杂原生组件(地图、WebView、专业SDK)。
核心技术 FlutterEngine 管理、FlutterActivity/FlutterViewController、平台通道 AndroidView/UiKitViewPlatformViewFactory、平台通道
主要挑战 引擎生命周期管理路由和数据同步统一导航体验 性能开销手势冲突键盘处理

选择哪种混合开发模式,完全取决于你的项目现状和业务需求。Flutter 提供了强大而灵活的工具来应对这两种场景。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐