在跨平台开发的实际场景中,纯 Flutter 应用往往无法满足所有业务需求 —— 例如调用平台专属的硬件能力、复用已有原生代码资产、接入仅支持原生的第三方 SDK。此时,Flutter 混合开发就成为了最优解。它允许开发者在原生 Android(Kotlin/Java)或 iOS(Swift/Objective-C)工程中嵌入 Flutter 模块,实现 “原生 + 跨平台” 的协同开发模式。

本文将从混合开发架构设计原生与 Flutter 通信页面路由管理资源与权限共享四个核心维度,结合实战案例讲解 Flutter 与原生工程的无缝集成方案,帮助开发者高效落地混合开发项目。

一、混合开发核心架构与适用场景

1. 两种核心集成架构

Flutter 混合开发有两种主流架构模式,开发者可根据项目需求选择:

架构模式 实现方式 优点 缺点 适用场景
原生工程嵌入 Flutter 模块 以原生工程为主工程,Flutter 作为模块被引入 可充分复用原生代码和 SDK,原生能力不受限 Flutter 页面需依赖原生工程编译运行 已有成熟原生应用,需新增跨平台页面
Flutter 工程接入原生插件 以 Flutter 工程为主工程,通过自定义插件封装原生能力 跨平台代码占比高,开发效率高 原生能力需封装为插件,有一定开发成本 新应用开发,需少量调用原生能力

本文重点讲解原生工程嵌入 Flutter 模块的方案,这也是企业级项目中最常用的混合开发模式。

2. 混合开发适用场景

  • 复用原生代码资产:已有原生应用的业务逻辑(如支付、登录)无需重构,直接供 Flutter 调用;
  • 调用平台专属能力:如 Android 的指纹识别、iOS 的 ARKit、蓝牙设备通信等 Flutter 官方插件未覆盖的功能;
  • 接入原生第三方 SDK:如某些硬件厂商提供的 SDK 仅支持原生开发,需通过混合开发集成;
  • 渐进式迁移:将原生应用的页面逐步替换为 Flutter 页面,降低重构风险。

二、实战准备:环境配置与工程创建

1. 前置环境

  • Flutter 3.20+、Android Studio Hedgehog+、Xcode 15+;
  • 原生工程:Android(Kotlin)或 iOS(Swift)工程;
  • 开启 Flutter 混合开发支持:执行 flutter config --enable-android-embedding-v2 和 flutter config --enable-ios-embedding-v2

2. 创建 Flutter 模块

在原生工程同级目录下,创建 Flutter 模块:

# 创建Flutter模块(注意:不要使用flutter create app,避免创建全量工程)
flutter create -t module flutter_module

创建完成后,得到一个标准的 Flutter 模块工程,核心目录结构如下:

flutter_module/
├── android/        # Android平台配置
├── ios/            # iOS平台配置
├── lib/            # Flutter业务代码
├── pubspec.yaml    # 依赖配置
└── .android/       # 隐藏的Android构建目录

三、步骤 1:Android 原生工程集成 Flutter 模块

1. 配置 settings.gradle

在原生 Android 工程的settings.gradle中,添加 Flutter 模块的依赖:

// 引入Flutter模块
setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'flutter_module/.android/include_flutter.groovy'
))

2. 配置 app/build.gradle

在原生工程的app/build.gradle中,添加 Flutter 依赖:

dependencies {
    // 其他原生依赖
    implementation project(':flutter')
}

3. 原生页面嵌入 Flutter 页面

在 Android 原生工程中,通过FlutterActivityFlutterFragment嵌入 Flutter 页面。

方式 1:通过 FlutterActivity 启动独立 Flutter 页面
// 原生Kotlin代码:MainActivity.kt
import io.flutter.embedding.android.FlutterActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 点击按钮跳转到Flutter页面
        findViewById<Button>(R.id.btn_flutter).setOnClickListener {
            val intent = FlutterActivity
                .withNewEngine()
                .initialRoute("/home") // 指定Flutter初始路由
                .build(this)
            startActivity(intent)
        }
    }
}
方式 2:通过 FlutterFragment 嵌入 Flutter 页面(与原生页面共存)
// 原生Kotlin代码:FlutterContainerActivity.kt
import io.flutter.embedding.android.FlutterFragment

class FlutterContainerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flutter_container)
        
        // 创建FlutterFragment
        val flutterFragment = FlutterFragment.withNewEngine()
            .initialRoute("/profile")
            .build()
        
        // 将FlutterFragment添加到原生布局的FrameLayout中
        supportFragmentManager.beginTransaction()
            .replace(R.id.fl_container, flutterFragment)
            .commit()
    }
}

对应的原生布局文件activity_flutter_container.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="原生头部导航栏"
        android:gravity="center"/>
    
    <FrameLayout
        android:id="@+id/fl_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
</LinearLayout>

四、步骤 2:iOS 原生工程集成 Flutter 模块

1. 通过 CocoaPods 集成 Flutter 模块

在 iOS 原生工程的Podfile中添加 Flutter 模块依赖:

# 指定Flutter模块路径
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'NativeiOSApp' do
  # 引入Flutter相关Pods
  install_all_flutter_pods(flutter_application_path)
  # 其他原生依赖
end

执行pod install,完成 Flutter 模块的集成。

2. 原生页面嵌入 Flutter 页面

方式 1:通过 FlutterViewController 启动 Flutter 页面
// 原生Swift代码:ViewController.swift
import UIKit
import Flutter

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 点击按钮跳转到Flutter页面
        let btn = UIButton(type: .system)
        btn.setTitle("跳转到Flutter页面", for: .normal)
        btn.addTarget(self, action: #selector(openFlutterPage), for: .touchUpInside)
        btn.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
        view.addSubview(btn)
    }
    
    @objc func openFlutterPage() {
        // 创建Flutter引擎
        let flutterEngine = FlutterEngine(name: "my_flutter_engine")
        flutterEngine.run(withEntrypoint: nil, initialRoute: "/home")
        
        // 创建FlutterViewController
        let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        present(flutterVC, animated: true)
    }
}
方式 2:将 Flutter 视图嵌入原生 View(与原生 UI 共存)
// 原生Swift代码:FlutterContainerVC.swift
import UIKit
import Flutter

class FlutterContainerVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 原生头部导航栏
        let navBar = UILabel(frame: CGRect(x: 0, y: 80, width: view.bounds.width, height: 50))
        navBar.text = "原生头部导航栏"
        navBar.textAlignment = .center
        view.addSubview(navBar)
        
        // 创建Flutter引擎
        let flutterEngine = FlutterEngine(name: "my_flutter_engine")
        flutterEngine.run(withEntrypoint: nil, initialRoute: "/profile")
        
        // 创建Flutter视图
        let flutterView = FlutterView(frame: CGRect(x: 0, y: 130, width: view.bounds.width, height: view.bounds.height - 130))
        let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        flutterVC.view = flutterView
        addChild(flutterVC)
        view.addSubview(flutterView)
    }
}

五、核心能力:原生与 Flutter 的双向通信

混合开发的关键是原生与 Flutter 的双向数据交互,Flutter 提供了MethodChannel(方法调用)、EventChannel(事件流)、BasicMessageChannel(消息传递)三种通信方式,其中MethodChannel是最常用的。

1. 通信原理

  • MethodChannel:基于异步消息传递机制,支持原生与 Flutter 互相调用方法并返回结果;
  • 通信标识:两端必须使用相同的Channel Name(如com.example.flutter/native),否则无法建立通信;
  • 数据类型:支持基础数据类型(int、String、bool)和集合类型(List、Map),复杂对象需转换为 Map 传递。

2. 实战:Flutter 调用原生能力(以获取设备信息为例)

步骤 1:原生端实现方法(Android)
// 原生Kotlin代码:在FlutterActivity中配置MethodChannel
class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.flutter/device_info"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 注册MethodChannel
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "getDeviceModel" -> {
                    // 获取设备型号
                    val deviceModel = android.os.Build.MODEL
                    result.success(deviceModel)
                }
                else -> {
                    result.notImplemented()
                }
            }
        }
    }
}
步骤 2:原生端实现方法(iOS)
// 原生Swift代码:在FlutterViewController中配置MethodChannel
class ViewController: FlutterViewController {
    let CHANNEL = "com.example.flutter/device_info"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 注册MethodChannel
        let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: flutterEngine!.binaryMessenger)
        methodChannel.setMethodCallHandler { [weak self] call, result in
            guard let self = self else { return }
            if call.method == "getDeviceModel" {
                // 获取设备型号
                let deviceModel = UIDevice.current.modelName
                result(deviceModel)
            } else {
                result(FlutterMethodNotImplemented)
            }
        }
    }
}

// 扩展:获取iOS设备具体型号
extension UIDevice {
    var modelName: String {
        // 实现设备型号获取逻辑,此处省略
        return "iPhone 15 Pro"
    }
}
步骤 3:Flutter 端调用原生方法
// Flutter Dart代码
import 'package:flutter/services.dart';

class DeviceInfoService {
  static const MethodChannel _channel = MethodChannel('com.example.flutter/device_info');

  // 调用原生方法获取设备型号
  static Future<String> getDeviceModel() async {
    try {
      final String model = await _channel.invokeMethod('getDeviceModel');
      return model;
    } on PlatformException catch (e) {
      return "获取设备信息失败:${e.message}";
    }
  }
}

// 在Flutter页面中使用
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设备信息')),
      body: Center(
        child: FutureBuilder<String>(
          future: DeviceInfoService.getDeviceModel(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Text('设备型号:${snapshot.data}');
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
    );
  }
}

3. 实战:原生调用 Flutter 方法

原生调用 Flutter 方法的流程与上述相反,核心是 Flutter 端注册 MethodChannel 并监听方法调用。

// Flutter Dart代码:注册MethodChannel,监听原生调用
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  static const MethodChannel _channel = MethodChannel('com.example.flutter/native_call');
  String _message = "等待原生调用";

  @override
  void initState() {
    super.initState();
    // 监听原生方法调用
    _channel.setMethodCallHandler((call) async {
      if (call.method == "showMessage") {
        String msg = call.arguments as String;
        setState(() {
          _message = msg;
        });
        return "Flutter已收到消息";
      } else {
        throw MissingPluginException();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(child: Text(_message));
  }
}

六、进阶优化:混合开发核心问题解决方案

1. 路由管理:原生与 Flutter 页面跳转

在混合开发中,页面跳转分为三种场景:

  • 原生→Flutter:通过FlutterActivity/FlutterViewController,指定initialRoute
  • Flutter→原生:通过 MethodChannel 调用原生方法,由原生端处理跳转;
  • Flutter→Flutter:使用 Flutter 自带的Navigator管理。

示例:Flutter 跳转到原生页面

// Flutter端通过MethodChannel触发跳转
static Future<void> openNativeSettingPage() async {
  await _channel.invokeMethod('openSettingPage');
}

// Android原生端处理跳转
when (call.method) {
    "openSettingPage" -> {
        val intent = Intent(this, SettingActivity::class.java)
        startActivity(intent)
        result.success(null)
    }
}

2. 资源共享:原生与 Flutter 资源互通

  • 图片资源:Flutter 可通过AssetImage访问自身assets目录资源,原生资源需通过 MethodChannel 传递路径;
  • 字符串资源:建议使用统一的远程配置,避免两端重复维护;
  • 主题样式:通过 MethodChannel 同步原生主题(如深色模式)到 Flutter 端。

3. 权限管理:统一权限申请流程

  • 原生权限:由原生端统一申请,Flutter 通过 MethodChannel 查询权限状态;
  • 权限回调:原生端申请权限后,通过 EventChannel 将结果同步到 Flutter 端。

4. 性能优化:避免 Flutter 引擎重复初始化

  • 引擎复用:在原生工程中创建全局 Flutter 引擎,避免每次跳转 Flutter 页面都初始化引擎;
  • 懒加载引擎:在应用启动后延迟初始化 Flutter 引擎,减少启动耗时。

七、总结

Flutter 混合开发的核心是 **“原生为主,Flutter 为辅”,通过合理的架构设计和通信机制,实现原生与 Flutter 的无缝协同。在实际项目中,开发者需要重点关注工程集成、双向通信、路由管理 ** 三个核心环节,同时结合项目特点选择合适的集成方案。

混合开发既保留了原生应用的性能和平台能力,又发挥了 Flutter 跨平台开发的效率优势,是企业级应用实现 “渐进式跨平台迁移” 的最佳路径。

欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐