把Flutter引入现有Android应用时,团队往往不需要推翻重写,更常见的做法是让Flutter先负责某一个业务模块,比如活动页、商品详情页,其余部分继续沿用成熟的Android原生代码。这种渐进式迁移的思路对应的正是Flutter官方提供的add-to-app方案。要理解这套方案,关键是抓住三个核心概念:Flutter模块的编译产物、承载Flutter页面的容器,以及原生与Dart之间的通信通道。

一、创建Flutter模块并接入Android工程
混合开发的第一步不是在Android工程里加依赖,而是单独创建一个Flutter模块。Flutter模块与普通Flutter应用的区别在于它没有独立的Android壳工程,编译产物是aar或者直接以源码形式被宿主工程引用。执行命令flutter create -t module my_flutter后,会得到一个包含Dart代码和pubspec.yaml的模块目录。
接下来在Android工程的settings.gradle中配置模块包含关系。这里有个容易踩坑的地方:官方推荐使用include_flutter.groovy脚本完成绑定,它能保证每次构建时Flutter依赖自动同步,避免手动维护版本。配置完成后,宿主app模块的build.gradle中只需要加上implementation project(':flutter')即可。
// settings.gradle setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'my_flutter/.android/include_flutter.groovy' )) include ':my_flutter'
需要注意模块目录与Android工程的相对位置,上面的示例中my_flutter与宿主工程同级放置。如果团队使用多模块仓库或者远程aar的方式接入,则要把Flutter模块打成aar上传到私有maven仓库,宿主通过坐标依赖。两种方式各有取舍:源码接入调试方便、支持热重载,适合开发期;aar接入构建隔离好、编译速度快,适合发布和跨团队协作。另外记得在gradle.properties中开启buildCache和相关代理配置,能明显提升增量构建速度。
二、选择合适的页面容器:FlutterActivity还是FlutterFragment
Flutter为Android端提供了两种主要容器。FlutterActivity是最简单的接入方式,直接在AndroidManifest.xml中注册后,通过Intent跳转即可打开一个全屏的Flutter页面。如果业务只需要整页展示Flutter内容,这是首选方案,代码量最少,维护成本也最低。
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/Theme.AppCompat"
android:windowSoftInputMode="adjustResize" />
而当需要在原生布局中嵌入Flutter视图,比如做一个底部原生导航栏、上方Flutter内容区的结构时,就要用FlutterFragment。它把Flutter渲染区域包装成Fragment,可以灵活嵌入ViewPager、ViewPager2或者任意Fragment容器中。要注意的是,同一个FlutterEngine只能绑定一个渲染目标,如果一个页面里同时出现两个FlutterFragment,必须为它们各自创建独立的引擎实例,否则会抛出异常。
除了以上两种,还有FlutterView可以直接加到任意ViewGroup中,灵活性最高但要自行管理生命周期与Surface的挂载卸载,官方并不推荐在常规业务中使用。选择容器时的原则很朴素:能用Activity就不折腾Fragment,能用Fragment就不直接操作View,层级越往上生命周期管理越省心。
三、原生与Flutter的通信:MethodChannel实战
页面嵌进来了,接下来绕不开通信问题。Flutter提供了三种通道:MethodChannel用于方法调用,EventChannel用于事件流,BasicMessageChannel用于双向消息传递。绝大多数场景用MethodChannel就够了,比如Flutter页面调用原生的登录能力、获取设备信息,或者原生主动通知Flutter刷新数据。
// Android端代码
MethodChannel channel = new MethodChannel(
flutterEngine.getDartExecutor().getBinaryMessenger(), "com.example.bridge");
channel.setMethodCallHandler((call, result) -> {
switch (call.method) {
case "getUserToken":
result.success(TokenManager.getToken());
break;
case "openNativePage":
String target = call.argument("pageName");
Router.navigate(context, target);
result.success(null);
break;
default:
result.notImplemented();
}
});
// Flutter端代码
static const _channel = MethodChannel('com.example.bridge');
Future<String?> getUserToken() async {
try {
final token = await _channel.invokeMethod<String>('getUserToken');
return token;
} on PlatformException catch (e) {
debugPrint('调用失败: ${e.message}');
return null;
}
}
通道名称必须两端完全一致,建议用包名加业务含义的命名方式避免冲突。还有一个细节容易被忽略:MethodChannel的handler默认运行在主线程,如果原生侧处理逻辑涉及耗时操作,要自己切换线程,同时注意result只能调用一次,重复调用会直接崩溃。
反向通信也是常见需求,比如原生收到推送后通知Flutter页面更新。做法是在Flutter端给同一个channel注册handler,原生侧通过invokeMethod调用Dart方法,参数与返回值同样经过标准消息编解码器序列化,支持Map、List等基础数据结构,复杂数据建议两端统一用JSON字符串传递。
四、混合栈的路由管理与性能优化
当Flutter页面越来越多,路由管理就成了架构设计的核心问题。Flutter内部有自己的Navigator路由体系,原生侧也有自己的任务栈,两者天然隔离。如果放任不管,用户在Flutter页面里跳转多次后,按返回键的体验会和原生页面不一致。常见的解法有两种:一是所有跨技术栈的跳转都收口到原生路由,Flutter内部只处理自身模块内的页面切换;二是引入统一的混合路由框架,把Flutter的路由栈镜像同步到原生栈中,保证返回键行为一致。对于以原生为主的工程,第一种方案侵入性更小,推荐优先考虑。
性能方面,FlutterEngine的创建是混合开发最大的开销点。引擎初始化涉及Dart VM启动、Dart代码加载和渲染管线搭建,冷启动可能消耗数百毫秒。Flutter SDK提供了预热机制,可以在Application中提前创建并缓存引擎:
public class App extends Application {
public static FlutterEngine engine;
@Override
public void onCreate() {
super.onCreate();
engine = new FlutterEngine(this);
engine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance()
.put("main_engine", engine);
}
}
打开页面时通过withCachedEngine("main_engine")复用缓存的引擎,首帧渲染时间能显著缩短。需要注意的是,预热引擎会常驻内存并占用一部分启动时间,是否使用要结合页面打开频率权衡。此外,开启混淆时记得保留Flutter相关类的proguard规则,避免release包出现找不到so库或者通道异常的问题。
最后建议在项目初期就确定好模块边界:哪些页面属于Flutter、通信接口有哪些、路由如何收口,把这些写进团队规范,后续维护成本会低很多。混合开发的本质不是技术堆叠,而是让两套体系在明确的边界内协作,边界越清晰,工程越稳。
Flutter混合开发Android嵌入Flutteradd-to-app修改时间:2026-09-04 18:50:56