
这段时间处理公司App接入Flutter的业务前前后后把混合开发的几条路子都趟了一遍。从最开始的“在原生Android页面里嵌一个Flutter活动页”到后来“从首页直接拉起独立的Flutter二级页”再到原生和Flutter之间来回传数据踩了不少坑也积累了一些经验。今天把这套Flutter混合开发流程整理出来主要围绕三块怎么把Android工程和Flutter工程关联起来、怎么在安卓页面里嵌入Flutter页面、怎么在安卓中启动Flutter页面。如果你正在做同样的技术选型或者准备接入这篇内容应该能帮你少走弯路。1. 混合开发的整体思路不是二选一而是渐进式融合1.1 为什么原生App要接Flutter先说背景。当时我们团队面对的问题是存量Android App是JavaKotlin混着写的业务模块非常多登录、支付、IM、推送全是原生实现而且稳定跑了三四年不可能推倒重写。但是新来的运营活动、电商频道、个人中心改版这些需求变化太频繁原生开发一周发一版的节奏根本跟不上产品团队催得紧开发团队也想提高迭代效率。引入Flutter成为最现实的选择——用Flutter承载新业务模块同时保留原有Native页面。混合开发的核心价值在于它让你不用做“全有或全无”的决定。你不需要先把整个App迁到Flutter而是可以先在一个页面试水跑通了再逐步扩大范围。比如我们上线的第一个Flutter页面是运营活动页前一周还在做原生切到Flutter之后UI还原效率明显提升热重载对调试的帮助也很大。这种渐进式迁移策略风险小见效快适合大多数已有Native产品的团队。1.2 两种关联路线Module方式与AAR方式在真正写代码之前你得先想清楚一个架构问题Android工程和Flutter工程之间以什么方式关联根据实际场景最常见的两种路线是Flutter Module方式和AAR产物方式。Flutter Module方式本质上是把Flutter工程作为一个子模块嵌入Android工程的构建体系Android工程在编译时直接读取Flutter源码目录参与整体Gradle构建。这种方式适合“原生和Flutter代码由同一批人维护、需要频繁联调”的团队。它的优点是修改Flutter代码后Android工程重新编译就能生效调试链路短不需要来回打产物包缺点是Android工程和Flutter工程的耦合度比较高如果你想把Flutter模块完全交给另一个团队维护这种耦合会带来协作上的摩擦。AAR方式则是先把Flutter工程用flutter build aar命令打包成一个Android依赖库然后像引入普通Maven依赖一样把AAR引入原生工程。这种方式的好处是解耦非常彻底Flutter团队和Android团队可以各自独立发版原生工程不需要关心Flutter源码长什么样。缺点是每次Flutter代码变更后都要重新打AAR、传仓库、更新依赖版本迭代节奏快的阶段会显得繁琐。我们团队最终选了Module方式原因是团队成员同时维护Flutter代码和原生代码联调频率极高追求的是改完就能跑的效率。如果你们团队是前后端分离、Flutter专门由一个小组维护那AAR方案会更合适。1.3 集成模式选型对比为了方便决策我把自己调研时整理的对比表放出来大家可以参考维度Module方式AAR方式FlutterView直接嵌入代码联调效率高改完直接编译低需要重新打包中适合单页面工程耦合度高低中业务隔离一般好一般接入成本中低高适合场景团队共同维护、频繁迭代独立团队协作、稳定发版临时页面、Demo演示这个表格里FlutterView直接嵌入属于最底层的做法灵活性最高但你需要自己管理FlutterEngine的生命周期接入成本不小。后面会详细说FlutterView的使用场景但日常混合开发我更推荐以FlutterFragment和FlutterActivity为主。2. 前期准备Flutter环境与Android工程关联2.1 Flutter SDK、Android环境与多版本管理开始之前先把环境准备好。Flutter SDK建议下载stable渠道的稳定版解压后设置好PATH。Windows和macOS都建议用flutter doctor检查一遍依赖Android SDK、Android Studio、JDK这些基础环境缺一不可。这里有个小建议如果你们团队同时维护多个Flutter项目不同项目可能依赖不同版本的Flutter SDK千万别在公共环境里只装一个固定版本我后面会单独讲FVM多版本管理方案。Flutter SDK下载慢、网络不稳定这个问题很多新手会卡住。解决办法是配置环境变量把PUB_HOSTED_URL和FLUTTER_STORAGE_BASE_URL指向国内可访问的镜像仓库这是官方认可的加速方式配置完重开终端重新执行flutter doctor就能看出来。如果你需要同时管理多个版本的Flutter直接用FVMFlutter Version Management一条fvm install装指定版本用fvm use切换项目级SDK版本这是当前比较主流的多版本管理思路。2.2 Module方式把Flutter工程作为Android工程的子模块Module方式的关联是整个混合开发的第一步也是很多教程讲不清楚的地方。我以实际命令为例完整走一遍流程。首先创建Flutter模块工程。注意这里创建的不是普通的flutter create应用工程而是flutter create -t module模块工程flutter create -t module --org com.example flutter_module执行完之后你会发现在flutter_module目录下并没有生成完整的android应用工程而是生成了一个隐藏的.android目录以及一个lib/main.dart入口文件。main.dart就是你的Flutter业务代码入口.android目录是给Gradle构建用的辅助工程。接着在Android工程里引入Flutter模块。打开Android工程根目录的settings.gradle加入下面这段配置// settings.gradle def flutterProjectRoot rootProject.projectDir.parentFile.toPath() def pluginsFile new File(flutterProjectRoot, .flutter-plugins-dependencies) if (pluginsFile.exists()) { pluginsFile.withReader(UTF-8) { reader - properties.load(reader) } } setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, flutter_module/.android/include_flutter.groovy ))这段代码的核心作用是让Android工程的Gradle构建过程知道Flutter模块在哪并把Flutter模块作为一个子工程include进来。其中include_flutter.groovy是Flutter模块自动生成的脚本不要手动改动它。最后在Android工程的app/build.gradle中依赖这个Flutter模块dependencies { implementation project(:flutter) }这里要注意Flutter模块对Android工程依赖的是名为flutter的Project不是flutter_module。因为include_flutter.groovy里默认include的是Flutter SDK的gradle子工程这个细节第一次接触时很容易踩坑。2.3 AAR方式把Flutter工程打成原生依赖如果你决定走AAR路线操作会比Module方式更简单也更适合跨团队协作。第一步在Flutter模块工程中执行打包命令flutter build aar这个命令会自动构建debug、profile、release三种模式的AAR产物并在控制台输出本地仓库的路径。典型输出会在flutter_module/build/host/outputs/repo目录下生成对应的Maven仓库。第二步在Android工程根目录的settings.gradle中配置这个本地仓库dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url flutter_module/build/host/outputs/repo } } }第三步在app/build.gradle中按构建模式引入依赖dependencies { debugImplementation com.example.flutter_module:flutter_debug:1.0 profileImplementation com.example.flutter_module:flutter_profile:1.0 releaseImplementation com.example.flutter_module:flutter_release:1.0 }这里debug、profile、release三种模式要分开依赖因为Flutter的AAR产物是按模式区分的。如果你只引入了release版本本地调试时就会因为找不到对应依赖而报错。每次Flutter代码更新后都需要重新执行flutter build aar并同步版本号这个流程放在CI里会更省心。2.4 Flutter Gradle插件的新版配置要求这部分是很多人升级Flutter版本后必踩的坑。如果你在构建时看到这样一条报错You are applying Flutters main Gradle plugin imperatively using the apply script method, which is deprecated and will be removed说明你还在用老式的apply plugin方式去加载Flutter Gradle插件。如果项目使用新版Flutter3.16及以上Gradle插件推荐使用声明式的方式配置。在Android工程的根目录settings.gradle中加入pluginManagement { def flutterSdkPath { def properties new Properties() file(local.properties).withInputStream { properties.load(it) } def flutterSdkPath properties.getProperty(flutter.sdk) assert flutterSdkPath ! null, flutter.sdk not found in local.properties return flutterSdkPath }() includeBuild($flutterSdkPath/packages/flutter_tools/gradle) repositories { google() mavenCentral() gradlePluginPortal() } }然后在项目根目录build.gradle中用pluginsDSL替代原先的apply pluginplugins { id com.android.application version 8.1.0 apply false id dev.flutter.flutter-gradle-plugin version 1.0.0 apply false }在app/build.gradle中再应用plugins { id com.android.application id dev.flutter.flutter-gradle-plugin }这样配置之后Flutter插件就会通过flutter.sdk路径自动加载不需要手动写apply脚本。注意local.properties里的flutter.sdk指向你本地Flutter SDK的解压路径这个文件通常不会提交到Git团队其他成员拉代码后需要自己配置。3. 在安卓页面中嵌入Flutter页面从Fragment入手3.1 FlutterFragment嵌入的基本步骤工程关联好之后接下来就是实战环节在现有的安卓页面里嵌入一个Flutter页面。常见的做法是通过FlutterFragment把Flutter页面作为一个Fragment加载到原生Activity的布局中这样做的好处是保留了原生页面的导航框架Flutter只负责页面内容渲染不接管整个页面栈。先看布局假设你想在原生页面的上半部分放Flutter内容下半部分还是原生控件那么布局文件里先放一个FrameLayout作为Flutter容器的占位FrameLayout android:idid/flutter_container android:layout_widthmatch_parent android:layout_height300dp / Button android:idid/native_btn android:layout_widthwrap_content android:layout_heightwrap_content android:text我是原生按钮 /然后在原生Activity中把FlutterFragment动态添加到这个容器里。注意这个Activity要继承自FragmentActivity或AppCompatActivity因为FlutterFragment依赖AndroidX FragmentManagerpublic class MainActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentTransaction tx getSupportFragmentManager().beginTransaction(); FlutterFragment flutterFragment FlutterFragment.withNewEngine() .initialRoute(activity_page) .build(); tx.add(R.id.flutter_container, flutterFragment); tx.commit(); } }这里withNewEngine()表示每次创建FlutterFragment时都新建一个FlutterEngine适用于页面不复杂、偶尔出现的场景。initialRoute是Flutter端路由名的对应参数Flutter里可以通过命名路由解析它。对应Flutter侧的main.dart用onGenerateRoute来分发不同的routevoid main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return MaterialApp( onGenerateRoute: (settings) { if (settings.name activity_page) { return MaterialPageRoute(builder: (context) const ActivityPage()); } return MaterialPageRoute(builder: (context) const HomePage()); }, ); } }这样你就能在原生页面里看到一个局部的Flutter页面了同时原生按钮还能继续点击完全不影响原本的交互逻辑。3.2 FlutterEngine的选择新建、缓存、还是共享刚才的代码用了withNewEngine()它是创建FlutterFragment最简单的方式但不是最高效的方式。FlutterEngine的创建和Dart isolate的初始化是混合开发启动耗时的主要来源如果你对页面性能有要求就需要深入了解FlutterEngine的三种使用策略。第一种直接用withNewEngine()。每次创建Fragment时都新建FlutterEngine优点是无状态、互不干扰缺点是启动慢、内存占用高完全不适合频繁打开的页面。第二种使用FlutterEngineCache缓存预创建的Engine。你可以先在Application或其他合适的时机创建好一个FlutterEngine并预热FlutterEngine flutterEngine new FlutterEngine(this); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); FlutterEngineCache.getInstance().put(my_engine, flutterEngine);然后在创建FlutterFragment时直接使用这个缓存的EngineFlutterFragment flutterFragment FlutterFragment.withCachedEngine(my_engine) .build();这种方案的好处是页面打开速度极快因为Engine和Dart isolate已经提前创建好跳过了最耗时的初始化阶段。缺点是所有复用这个Engine的页面共享同一个Dart isolate路由栈是共用的你要在Flutter侧处理好页面跳转逻辑。第三种使用FlutterEngineGroup创建共享引擎。如果你需要多个Flutter页面或Fragment但又不想每个都重新走一遍Engine初始化可以用FlutterEngineGroup创建共享同构的Engine。它内部会复用同一套Dart runtime和基础组件内存占用比多个独立Engine低官方也推荐这种方案用于多实例场景。不过FlutterEngineGroup的使用相对复杂适合页面数量多、且页面间需要共享资源的复杂应用初期可以先不碰。3.3 生命周期同步与内存释放用FlutterFragment嵌入页面时Fragment的生命周期和FlutterEngine是自动绑定的不需要你手动干预。但如果你直接使用FlutterView或者手动持有FlutterEngine内存释放就得上心了。一个常见误区是Activity销毁后没有释放FlutterEngine。虽然Dart有垃圾回收但FlutterEngine持有大量原生资源Surface、纹理、平台通道如果不主动销毁很容易造成内存泄漏。如果你用withNewEngine()创建了FlutterFragment可以在Fragment的onDestroy里考虑是否需要销毁对应的EngineOverride public void onDestroy() { super.onDestroy(); // 如果这个engine是只为当前页面创建的销毁前先移除缓存 FlutterEngineCache.getInstance().remove(my_engine); flutterEngine.destroy(); }但要注意不要在任何地方随便destroy()如果这个Engine还被其他页面引用销毁后会直接导致空白页或崩溃。我一般遵循一个原则如果Engine在Application里创建且全局复用就不在某个页面销毁如果Engine是页面自定义创建则页面销毁时一并销毁。4. 在安卓中启动独立的Flutter页面4.1 FlutterActivity启动页面嵌入Flutter页面的另一种常见需求是希望从某个原生按钮直接跳转到一个独立的Flutter页面也就是说整个页面全是Flutter渲染不保留原生页面结构。这个场景用FlutterActivity是最标准的做法。最简单的方式是这样Intent intent FlutterActivity.createDefaultIntent(this); startActivity(intent);createDefaultIntent会启动默认的FlutterActivity并加载Flutter工程里默认的Dart入口。如果你的Flutter工程有多个入口、多个路由就需要用withNewEngine()的构建方式传入路由参数Intent intent FlutterActivity .withNewEngine() .initialRoute(goods_detail?id1001) .buildCurrentIntent(this); startActivity(intent);启动后Flutter侧的MaterialApp会根据路由名加载对应页面。这里补充一个细节如果你不希望启动时出现白屏或闪屏记得在AndroidManifest.xml中为FlutterActivity配置主题使用Launcher页的windowBackground或者直接设置一个不透明的背景色能明显提升启动观感。4.2 路由、参数传递与返回结果路由参数除了放在initialRoute里还有一种更优雅的方式是在启动FlutterActivity时通过Intent的Extra传递数据尤其是那些不适合放在URL里的复杂对象。比如Intent intent FlutterActivity .withNewEngine() .initialRoute(user_profile) .buildCurrentIntent(this); intent.putExtra(user_id, u_12345); startActivity(intent);Flutter侧在onGenerateRoute里可以通过MaterialPageRoute的settings.arguments读取Intent携带的Extra数据onGenerateRoute: (settings) { if (settings.name user_profile) { final args settings.arguments as Map?; final userId args?[user_id] ?? ; return MaterialPageRoute(builder: (context) UserProfilePage(userId: userId)); } return MaterialPageRoute(builder: (context) const HomePage()); }返回结果的处理也经常被问到。原生Activity通过startActivityForResult启动FlutterActivityFlutter页面里通过Navigator.pop带数据返回然后原生侧在onActivityResult中接收。这里关键是频道要统一数据格式建议约定为String、int、bool这些基础类型避免解析复杂对象。4.3 多页面路由与Engine复用优化当FlutterActivity内部存在多个页面层级时比如从Flutter首页跳转到Flutter详情页再跳到支付页你会发现这些页面共享同一个FlutterEngine路由栈由Flutter侧管理原生只需要启动一次FlutterActivity。这种模式是最高效的原生到Flutter只启动一次后续页面跳转完全在Flutter内部完成不会反复创建Engine和Dart isolate页面切换体验也流畅不少。但相应地你要在Flutter侧维护好页面栈。Navigator.push和Navigator.pop的使用要规范尤其是在支付、登录这类需要返回结果的场景不要出现页面栈越积越深的问题。我见过一个项目用户在Flutter页面里反复跳转几十层返回键一层层弹体验非常差。后来我们在Flutter侧统一封装了路由管理器所有二级页面都用pushReplacement或pushAndRemoveUntil保证页面栈深度可控。5. 原生与Flutter的通信MethodChannel、EventChannel5.1 MethodChannel双向调用嵌入Flutter页面只是第一步页面跑起来之后原生和Flutter之间一定需要通信。最常见的通信方式是MethodChannel它支持Flutter调用原生方法也支持原生调用Flutter端方法。先看Flutter调用原生的场景。比如Flutter页面上有个按钮点击后需要读取Android设备型号并展示Flutter侧这样写import package:flutter/services.dart; class NativeBridge { static const MethodChannel _channel MethodChannel(com.example.app/bridge); static FutureString? getDeviceInfo() async { return await _channel.invokeMethod(getDeviceInfo); } }原生侧在创建FlutterEngine的地方注册对应的MethodChannelnew MethodChannel( flutterEngine.getDartExecutor().getBinaryMessenger(), com.example.app/bridge ).setMethodCallHandler((call, result) - { if (call.method.equals(getDeviceInfo)) { result.success(Build.MANUFACTURER Build.MODEL); } else { result.notImplemented(); } });这里有几个注意点。result.success只能调用一次不能多次回调。如果回调不能立刻给出结果也一定要保存result并在异步任务结束后调用否则Flutter侧的Future会一直挂起造成页面卡住感。另外原生回调的线程默认不是主线程如果你更新UI必须切到主线程再操作。原生调用Flutter侧方法的场景也很常见。比如原生监听到某个广播事件需要通知Flutter页面刷新数据。原生侧通过MethodChannel的invokeMethod调用Flutter侧注册的方法MethodChannel channel new MethodChannel( flutterEngine.getDartExecutor().getBinaryMessenger(), com.example.app/bridge ); channel.invokeMethod(onNativeEvent, refresh_data);Flutter侧在initState里注册监听override void initState() { super.initState(); _channel.setMethodCallHandler((call) async { if (call.method onNativeEvent) { // 刷新页面 } return null; }); }到这里双向调用的基本闭环就跑通了。需要注意的是MethodChannel的method名称在整个App内要尽量唯一并统一管理避免不同页面重复命名产生冲突。5.2 EventChannel事件订阅有些场景更适合用事件流来通信比如电量变化、网络状态变化、传感器数据这类持续性的数据推送。Flutter侧用EventChannel来订阅原生事件原生侧通过EventSink持续向Flutter发送事件。Flutter侧订阅EventChannel _eventChannel const EventChannel(com.example.app/events); StreamSubscription? _sub; void _startListen() { _sub _eventChannel.receiveBroadcastStream().listen((event) { // 处理事件 }); }原生侧实现StreamHandler在onListen时开始发送事件在onCancel时停止new EventChannel( flutterEngine.getDartExecutor().getBinaryMessenger(), com.example.app/events ).setStreamHandler(new EventChannel.StreamHandler() { Override public void onListen(Object arguments, EventChannel.EventSink events) { // 事件开始监听时发送当前状态 events.success(current_battery_level: 80); } Override public void onCancel(Object arguments) { // 停止事件发送 } });EventChannel适合单向数据流不适合作为请求/响应式调用。我一般会在需求评审阶段就判断好数据类型如果是“问一句答一句”用MethodChannel如果是“一直往外推数据”用EventChannel。选错了通信方式后面维护成本会成倍增加。5.3 通道命名规范与调试技巧混合开发的通信通道多了以后命名不规范非常痛苦。我建议采用com.company.product.feature的格式比如com.example.app.user、com.example.app.payment。这样在Logcat里一眼能看出是哪个业务模块在通信排查问题不需要翻代码。调试平台通道时有一个特别有用的技巧。Flutter端和Android端在通道名称相同、Method名相同但参数类型不一致时报错信息往往比较模糊。遇到这种情况先在Flutter侧打印MethodChannel的调用参数再在Android侧打印收到的参数对照一下两边类型和字段名基本能定位问题。另一个高频坑是int和double类型在Java和Dart之间自动转换的细节比如Java的int到Dart侧变成int但如果传到Dart侧的是null一定要在Dart侧做好判空不然空指针异常会让你排查半天。6. 实战避坑3个高频报错与解决建议6.1 VS Code运行Flutter时找不到Visual Studio工具链在VS Code里开发Flutter如果你跑flutter run底部弹出设备选择列表时没有手动指定设备Flutter默认会把windows桌面端当作目标平台执行。这时候如果本机没有安装Windows桌面开发工具链就会报类似这样一段错误unable to find suitable Visual Studio toolc...很多人第一次看到这个报错会误以为是Flutter环境坏了其实不是。原因很简单Flutter需要Visual Studio的C工作负载来编译Windows桌面端。解决方式有两个。如果你只想开发Android在VS Code底部点击设备选择器把目标设备切换成Android模拟器或真机问题直接消失。如果你想保留Windows桌面端调试能力那就安装Visual Studio Build Tools安装时勾选“使用C的桌面开发”工作负载这个组件比较大需要几分钟。我自己开发时习惯在项目的launch.json里锁定设备类型避免每次运行还要手动选一次设备{ name: Flutter (Android), request: launch, type: dart, deviceId: emulator-5554 }这样按F5就直接跑Android模拟器不会再误启动Windows桌面构建。6.2 Flutter Gradle插件apply方式报错这个报错在前面配置模块时已经提过但实际项目里它的出现场景比你想象的更多。团队里如果有一个老Android工程原本用的是旧版Flutter插件某天升级了Flutter或AGP版本构建立马报红You are applying Flutters main Gradle plugin imperatively using the apply script method, which is deprecated and will be removed.我的处理流程是先检查根目录settings.gradle是否配置了pluginManagement如果没有先把Flutter SDK路径读取逻辑加进去然后把根目录和app/build.gradle里的apply plugin: ...改成pluginsDSL方式。改完之后重新Sync基本都能解决。这里要特别提醒local.properties中的flutter.sdk路径必须正确否则一切配置都白搭。因为你是用这个路径去 include Flutter的gradle插件的路径错了找不到插件所有关联方案都会失败。6.3 FileProvider路径异常与分区存储Flutter混合开发里还有一个很诡异的问题就是App启动时直接崩溃Logcat里报FileProvider路径异常类似content://com.某应用.fileprovider/external_path/android/data/...这样的Uri。这个问题多出现在Android 11及以上原因是分区存储机制限制了应用对外部存储目录的访问而部分三方SDK比如通讯录、客服SDK、扫码SDK通过FileProvider分享Android/data目录下的文件时就会触发限制。处理思路分几步。先检查AndroidManifest里是否声明了多个FileProvider若存在authorities冲突就合并或改掉其中一个的authorities。再检查是否需要兼容旧文件访问在manifest的application节点加android:requestLegacyExternalStoragetrue这只对旧版应用有效并不能根治新SDK的问题。最彻底的方式是升级到适配Android 11及以上分区存储的SDK版本或者在拿到文件路径时先复制到App私有目录再交给FileProvider处理。遇到这类问题不要被Logcat里长长的content Uri吓到先定位是哪个SDK发出的Uri再决定是升级SDK还是改造文件处理逻辑。6.4 多版本Flutter SDK管理的小建议最后说一个日常工作必不可少的工具FVM。团队里不同Android项目可能依赖不同Flutter版本A项目用3.13B项目用3.19如果只靠本地装一个版本每次切换项目都得重新安装SDK效率极低。FVM的简单用法如下# 安装指定版本 fvm install 3.19.0 # 在项目目录内指定版本 fvm use 3.19.0 # 使用FVM包装的flutter命令 fvm flutter pub get fvm flutter run关键点在于项目工程里生成的local.properties会记录SDK路径用FVM管理版本后每个项目的SDK路径由FVM的符号链接统一维护不会互相覆盖。团队协作时建议在项目根目录提交.fvmrc文件锁定Flutter版本其他人拉取代码后用fvm install一键还原环境避免因SDK版本不一致导致的构建差异。踩过几次版本不一致的坑之后我现在给团队定的规范是所有Flutter相关项目必须使用FVM且在README里写明推荐版本和fvm还原命令。这一条能省掉团队内部大量环境兼容问题。我在实际接入Flutter混合开发的过程中最大的体会是混合开发不是“把Flutter塞进Android”而是“让Flutter和Android各自发挥长处”。原生页面稳定Flutter页面灵活两者通过规范化的模块关联和平台通信协作才是长期可维护的状态。如果你准备在项目里启动混合开发我建议先拿一个低风险页面比如运营活动页或设置页练手把Module关联、FlutterFragment嵌入、MethodChannel通信这三条基础流程跑通再考虑大规模推广。最后再补充一个小技巧接入混合开发后每次发布前在CI环境里执行一次干净的flutter build aar保证产物和本地开发环境一致能提前拦截掉很多只在某台机器上能编过的隐患。