Flutter 不仅可以用于从零开始构建全新的应用程序,还可以作为模块集成到现有的原生 Android 或 iOS 应用中,实现混合开发。这种方式允许开发者逐步将 Flutter 引入到现有项目中,利用 Flutter 的高效开发和跨平台优势,同时保留原生应用的现有功能和代码库。
1. 为什么选择混合开发?
2. 集成 Flutter 到现有应用
Flutter 提供了两种主要的集成方式:
3. Add-to-App 核心概念
FlutterEngine 实例。FlutterEngine 的渲染结果显示在屏幕上。MethodChannel)变得尤为重要。它用于原生代码和 Flutter 代码之间的双向通信,例如原生代码调用 Flutter 页面,或 Flutter 页面调用原生功能。4. 集成步骤概览
Android 集成:
flutter create -t module my_flutter_module
settings.gradle 中引用 Flutter 模块。build.gradle 中添加 Flutter 模块的依赖。FlutterEngine 实例。FlutterActivity 或 FlutterFragment 来显示 Flutter UI。MethodChannel。iOS 集成:
FlutterEngine 实例。FlutterViewController 来显示 Flutter UI。MethodChannel。混合开发在许多大型企业级应用中非常常见,它允许团队在不完全重写现有应用的情况下,逐步引入 Flutter 的优势。
案例:在现有 Android 应用中嵌入一个 Flutter 页面
我们将演示如何在 Android 原生应用中启动一个 Flutter 页面,并实现原生与 Flutter 之间的简单通信。
前提条件:
步骤 1: 创建 Flutter 模块
在你的工作目录下,创建一个新的 Flutter 模块:
flutter create -t module my_flutter_module
这个命令会在 my_flutter_module 目录下创建一个 Flutter 模块项目。其中 my_flutter_module/.android/ 包含了 Android 集成所需的 Gradle 配置。
步骤 2: 将 Flutter 模块添加到原生 Android 项目
修改原生 Android 项目的 settings.gradle:
在原生 Android 项目的根目录下找到 settings.gradle 文件,添加以下内容:
// settings.gradle
include ":app"
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile, // 指向你的 Flutter 模块的父目录
'my_flutter_module/.android/include_flutter.groovy'
))
确保 settingsDir.parentFile 指向的是 my_flutter_module 所在的目录。如果 my_flutter_module 和你的原生 Android 项目在同一个父目录下,那么 settingsDir.parentFile 是正确的。
修改原生 Android 应用的 build.gradle:
在原生 Android 应用模块(通常是 app 模块)的 build.gradle 文件中,添加 Flutter 模块的依赖:
// app/build.gradle
dependencies {
implementation flutter.embed()
// ... 其他依赖
}
步骤 3: 在原生 Android 代码中启动 Flutter 页面
修改 MainActivity.java (或 MainActivity.kt):
// MainActivity.java
package com.example.myandroidapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;
public class MainActivity extends AppCompatActivity {
private static final String FLUTTER_ENGINE_ID = "my_flutter_engine";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 预加载 FlutterEngine,提高启动速度
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine);
Button openFlutterButton = findViewById(R.id.open_flutter_button);
openFlutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.build(MainActivity.this)
);
}
});
}
}
创建 activity_main.xml 布局文件:
在 app/src/main/res/layout/activity_main.xml 中添加一个按钮:
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/open_flutter_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="打开 Flutter 页面" />
</RelativeLayout>
步骤 4: 在 Flutter 模块中编写 UI (my_flutter_module/lib/main.dart)
// my_flutter_module/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // 用于 MethodChannel
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Module',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const FlutterModuleHomePage(),
);
}
}
class FlutterModuleHomePage extends StatefulWidget {
const FlutterModuleHomePage({super.key});
@override
State<FlutterModuleHomePage> createState() => _FlutterModuleHomePageState();
}
class _FlutterModuleHomePageState extends State<FlutterModuleHomePage> {
static const platform = MethodChannel('com.example.myandroidapp/message');
String _messageFromNative = '等待原生消息...';
@override
void initState() {
super.initState();
// 监听来自原生的消息
platform.setMethodCallHandler((call) async {
if (call.method == 'receiveMessage') {
setState(() {
_messageFromNative = call.arguments as String;
});
return 'Flutter 已收到消息';
}
return '未知方法';
});
}
Future<void> _sendMessageToNative() async {
try {
final String result = await platform.invokeMethod('sendMessage', {'message': 'Hello from Flutter!'});
print('原生返回: $result');
} on PlatformException catch (e) {
print('调用原生方法失败: ${e.message}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter 模块页面'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'这是嵌入在原生应用中的 Flutter 页面!',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
Text(
'来自原生应用的消息: $_messageFromNative',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16, color: Colors.green),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _sendMessageToNative,
child: const Text('发送消息给原生应用'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context); // 返回原生页面
},
child: const Text('返回原生应用'),
),
],
),
),
);
}
}
步骤 5: 实现原生与 Flutter 之间的通信 (Android)
在原生 Android 的 MainActivity.java (或 MainActivity.kt) 中添加 MethodChannel 的实现:
// MainActivity.java (续)
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends AppCompatActivity {
private static final String FLUTTER_ENGINE_ID = "my_flutter_engine";
private static final String CHANNEL = "com.example.myandroidapp/message";
private MethodChannel methodChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine);
// 初始化 MethodChannel
methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
methodChannel.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("sendMessage")) {
String message = call.argument("message");
// 处理来自 Flutter 的消息
System.out.println("原生收到来自 Flutter 的消息: " + message);
result.success("原生已收到消息: " + message);
} else {
result.notImplemented();
}
}
);
Button openFlutterButton = findViewById(R.id.open_flutter_button);
openFlutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.build(MainActivity.this)
);
// 在打开 Flutter 页面后,向 Flutter 发送消息
new Handler(Looper.getMainLooper()).postDelayed(() -> {
methodChannel.invokeMethod("receiveMessage", "Hello from Native Android!");
}, 1000); // 延迟发送,确保 Flutter 页面已加载
}
});
}
}
案例分析:
flutter create -t module my_flutter_module:创建 Flutter 模块,这是混合开发的基础。settings.gradle 和 app/build.gradle 配置:这些配置告诉 Gradle 如何找到并编译 Flutter 模块,并将其作为依赖添加到原生 Android 项目中。FlutterEngine 预加载:在 MainActivity 的 onCreate 中预加载 FlutterEngine 并缓存,可以显著减少 Flutter 页面首次启动的时间,提升用户体验。FlutterActivity.withCachedEngine():使用缓存的 FlutterEngine 启动 Flutter 页面。MethodChannel 通信:platform.invokeMethod("sendMessage", ...) 向原生发送消息,并通过 platform.setMethodCallHandler 监听原生发送过来的消息。methodChannel.setMethodCallHandler 监听来自 Flutter 的消息,并通过 methodChannel.invokeMethod("receiveMessage", ...) 向 Flutter 发送消息。这个案例展示了 Flutter 如何作为模块集成到现有 Android 应用中,并实现了原生与 Flutter 之间的双向通信。这种混合开发模式为大型应用的逐步现代化提供了强大的解决方案。
注意:iOS 集成步骤类似,但涉及 Xcode 项目配置和 Swift/Objective-C 代码。具体细节请参考 Flutter 官方文档。