99、Flutter与Android混合开发:Flutter Module集成、MethodChannel、PlatformView
说实话,Flutter 和 Android 混合开发这个话题,我琢磨了挺久。
很多团队不是不想用 Flutter,而是老项目已经跑了好几年,不可能全盘重写。这时候,把 Flutter 作为一个模块嵌进去,就成了最务实的选择。我自己在几个中型项目里都这么干过,踩的坑不少,但最终效果确实不错。
Flutter Module 集成:把 Flutter 当“插件”用
先说说最基础的事——怎么把 Flutter 塞进现有的 Android 项目里。
官方推荐的方式是创建 Flutter Module,而不是 Flutter App。说白了,Module 就是给原生项目准备的“零件”。
核心思路:Flutter Module 编译后会生成 AAR 包,Android 项目直接依赖它就行。
具体步骤其实不复杂:
- 在 Android 项目同级目录下,用
flutter create --template module my_flutter创建 Module。 - 在 Android 项目的
settings.gradle里加上:setBinding(new Binding([gradle: this]));evaluate(new File(settingsDir, '../my_flutter/.android/include_flutter.groovy')) - 然后在 app 的
build.gradle里添加依赖:implementation project(':flutter')
嗯,这里要注意一点。如果你用的是 Android 原生工程,而不是 Flutter 创建的 Android 目录,那路径可能会对不上。我遇到过好几次,最后发现是 include_flutter.groovy 这个文件没找到。解决办法很简单——检查一下相对路径是否正确。
我的习惯:把 Flutter Module 放在 Android 项目的根目录下,这样路径最短,也最不容易出错。
MethodChannel:让 Dart 和 Java/Kotlin 说上话
Module 集成好了,接下来就是通信问题。Flutter 和原生之间怎么传数据?
MethodChannel 就是干这个的。它像一根管道,两头分别连着 Dart 和原生代码。
我画了一张图,帮你理解它的工作流程:
你看,流程其实很清晰。Dart 端发一个请求,原生端收到后处理,再返回结果。
代码怎么写?我直接给你看核心部分。
Dart 端:
import 'package:flutter/services.dart';
class NativeBridge {
static const MethodChannel _channel = MethodChannel('com.example/bridge');
static Future<String> getBatteryLevel() async {
try {
final String result = await _channel.invokeMethod('getBatteryLevel');
return result;
} on PlatformException catch (e) {
return "Failed: ${e.message}";
}
}
}
Android 端(Kotlin):
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example/bridge"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
我曾经踩过的坑:MethodChannel 的名字一定要两边完全一致,大小写、标点都不能错。有一次我写成了 "com.example/bridge" 和 "com.example/Bridge",结果调试了一下午才发现是大小写问题。
PlatformView:把原生控件嵌入 Flutter
有些场景下,Flutter 自带的组件不够用。比如你要嵌入一个 WebView,或者用原生的地图 SDK。这时候就需要 PlatformView 了。
PlatformView 说白了,就是让 Flutter 的 Widget 树里,能直接放一个 Android 的 View。你想想看,这相当于在 Flutter 的画布上开了一个“窗口”,原生控件直接画在上面。
实现起来分两步:
- 在 Android 端创建一个
PlatformViewFactory。 - 在 Flutter 端通过
AndroidView或UiKitView来引用。
Android 端实现:
class NativeTextViewFactory : PlatformViewFactory {
override fun create(context: Context, id: Int, args: Any?): PlatformView {
return NativeTextView(context, id, args)
}
}
class NativeTextView(context: Context, id: Int, args: Any?) : PlatformView {
private val textView = TextView(context).apply {
text = "我是原生 TextView"
textSize = 24f
setTextColor(Color.BLUE)
}
override fun getView(): View = textView
override fun dispose() {}
}
然后在 MainActivity 里注册:
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("native_text_view", NativeTextViewFactory())
}
Flutter 端使用:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('PlatformView 示例')),
body: Center(
child: AndroidView(
viewType: 'native_text_view',
layoutDirection: TextDirection.ltr,
creationParams: null,
creationParamsCodec: null,
),
),
);
}
我的经验:PlatformView 在性能上会有一些损耗,因为 Flutter 和原生之间需要同步帧率。如果你的页面里 PlatformView 很多,建议做一下性能测试。我一般控制在 3 个以内,再多就容易掉帧。
三种方式的对比与选择
我把这三种方式整理成了一个表格,方便你对比:
| 方式 | 适用场景 | 通信方向 | 性能 | 复杂度 |
|---|---|---|---|---|
| Flutter Module | 整体嵌入 Flutter 页面 | 单向(原生→Flutter) | 高 | 低 |
| MethodChannel | 数据传递、调用原生 API | 双向 | 中 | 中 |
| PlatformView | 嵌入原生控件(地图、WebView) | 单向(Flutter→原生) | 中低 | 高 |
怎么选?其实很简单:
- 如果你只是想把 Flutter 页面作为一个整体展示,用 Module 就够了。
- 如果需要频繁传数据,比如获取设备信息、调用相机,那就用 MethodChannel。
- 如果非要用原生控件不可,再考虑 PlatformView。
我个人习惯:能用 MethodChannel 解决的,尽量不用 PlatformView。因为 PlatformView 的维护成本确实高一些,尤其是不同 Android 版本上的兼容性问题。我曾经在 Android 10 上跑得好好的,换到 Android 12 就闪退,最后发现是 PlatformView 的触摸事件分发机制变了。
避坑指南
最后,分享几个我实际项目中遇到的坑:
- MethodChannel 的线程问题:原生端的处理默认在 UI 线程,如果你要做耗时操作,记得开子线程,否则会卡界面。
- PlatformView 的键盘问题:如果 PlatformView 里有输入框,键盘弹出时可能会遮挡。我当时的解决方案是监听键盘事件,手动调整布局。
- Flutter Module 的版本同步:Flutter 和 Android 的 Gradle 版本要匹配,否则编译会报错。建议用 Flutter 官方推荐的版本组合。
嗯,差不多就这些了。混合开发这条路,说难不难,说简单也不简单。关键是理解清楚每种方式的定位,然后根据实际场景去选。你动手试试看,遇到问题再回来翻翻这篇文章,应该能解决大部分疑惑。