前言:
在原有的iOS工程中,集成flutter module也是一种常见的开发模式。可以保证之前业务的稳定性,新的模块完全可以使用flutter来实现。当然中间也会涉及到很多的问题,比如 引入flutter 方式,多引擎还是单引擎,iOS和flutter怎么交互等等。
一、引入方式
第一种方案: Flutter 官方已经给出的混编方案:
文档里写的比较清楚了的,这里就不再多做赘述,大意就是说现在我们只需要在iOS工程的podfile文件中添加如下命令。
# Uncomment the next line to define a global platform for your project
flutter_application_path = '../flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
platform :ios, '13.0'
target 'Project' do
# Comment the next line if you don't want to use dynamic frameworks
# use_frameworks!
# Pods for Project
install_all_flutter_pods(flutter_application_path)
pod 'AFNetworking', '~> 4.0.1'
pod 'SDWebImage','~> 5.19.7'
pod 'Masonry', '~> 1.1.0',:inhibit_warnings => true
pod 'YYModel'
pod 'YYCategories', '~> 1.0.4'
pod 'MJRefresh', '~> 3.5.0'
pod 'SVProgressHUD'
pod 'SDCycleScrollView'
target 'ProjectTests' do
inherit! :search_paths
# Pods for testing
end
target 'ProjectUITests' do
# Pods for testing
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
flutter_post_install(installer)
end
end
然后执行 pod install 即可,这种引入方式要求每个开发的人都需要在本地电脑配置好 flutter 环境,不然是跑不起来的。这种方式相对来说对于不需要关注 flutter 模块的开发者是很头疼的,比如第一次需要打开 flutter 代码,选择依赖的 flutterSDK,然后执行 flutter pub get ,然后在进入 iOS 目录下,执行 pod install。在比如flutter模块引入新的插件了,还得按照以上流程来一遍,很麻烦。
第二种方案: iOS集成flutter的编译产物
将 flutter 以 framework 的形式通过 Cocoapods 引入 iOS 工程,iOS 只需要关注 flutter 的编译产物,不需要配置本地环境,不需要关注 flutter 代码的变化。Podfile文件如下所示,flutter 更新完后只需要执行下 pod install 即可,对象原生项目0侵入。
# Uncomment the next line to define a global platform for your project
platform :ios, '13.0'
target 'Project' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for Project
pod 'FlutterPod',:git=>'https://gitee.com/TTGF/flutter_pod.git'
pod 'AFNetworking', '~> 4.0.1'
pod 'SDWebImage','~> 5.19.7'
pod 'Masonry', '~> 1.1.0',:inhibit_warnings => true
pod 'YYModel'
pod 'YYCategories', '~> 1.0.4'
pod 'MJRefresh', '~> 3.5.0'
pod 'SVProgressHUD'
pod 'SDCycleScrollView'
target 'ProjectTests' do
inherit! :search_paths
# Pods for testing
end
target 'ProjectUITests' do
# Pods for testing
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
end
1. 创建 iOS 工程
自行创建即可
2. 创建名字为‘ FlutterPod’的Pod库
$ cd ~/Desktop/(文件夹)
$ pod lib create FlutterPod
终端依次输入所需类型:
xingkunkun:FlutterForFW admin$ pod lib create MyFlutterPod
Cloning `https://github.com/CocoaPods/pod-template.git` into `MyFlutterPod`.
Configuring MyFlutterPod template.
------------------------------
To get you started we need to ask a few questions, this should only take a minute.
What platform do you want to use?? [ iOS / macOS ]
> ios
What language do you want to use?? [ Swift / ObjC ]
> objc
Would you like to include a demo application with your library? [ Yes / No ]
> no
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> none
Would you like to do view based testing? [ Yes / No ]
> no
What is your class prefix?
> Kevin
Running pod install on your new library.
创建完了之后如下图所示:
3. 在FlutterPod目录下创建 Flutter Module模块
创建完后如下图所示
4. 在 pubspec.yaml 文件中加入插件
在 pubspec.yaml 文件中加入开发所需的插件,以下是我的 pubspec.yaml 文件。
name: flutter_novel
description: "A new Flutter project."
version: 1.0.0+1
environment:
sdk: ^3.8.0
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
flutter_screenutil: ^5.9.3
pull_to_refresh: ^2.0.0
logger: ^1.3.0
dio: ^5.8.0+1
fluro: ^2.0.5
fluttertoast: ^8.2.12
flutter_easyloading: ^3.0.5
flutter_bloc: ^8.1.2
extended_image: ^10.0.1
status_bar_control:
git:
url: https://gitee.com/TTGF/status_bar_control.git
flutter_localizations:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
assets:
- assets/images/
- mock/
module:
androidX: true
androidPackage: com.example.flutter_novel
iosBundleIdentifier: com.example.flutterNovel
5. 编译
执行以下任意一个命令
$ flutter build ios --debug
或者
$ flutter build ios --release --no-codesign
会发现flutter_module 会生成一个build文件夹,里面装的就是需要的编译产物,如下图所示。
需要 debug 版本的执行 flutter build ios --debug 对应的文件夹就是 Debug-iphoneos
需要 release 版本的执行 flutter build ios --release 对应的文件夹就是 Release-iphoneos
然后将这些 framework 放在 flutter_pod 的一个文件夹里面上传 git 。
为了提高开发效率使用可以以下脚本,(自己手动操作也是没有问题的)。
在 flutter_module 模块下下创建脚本文件
$ cd ../flutter_module
$ touch compile_project.sh // 创建脚本
$ open compile_project.sh
将下面代码复制进 compile_project.sh
if [ -z $out ]; then
out='ios_frameworks'
fi
echo "准备输出所有文件到目录: $out"
echo "清除所有已编译文件"
find . -d -name build | xargs rm -rf
flutter clean
rm -rf $out
rm -rf build
source ~/.bash_profile
flutter clean
flutter pub get
addFlag(){
cat .ios/Podfile > tmp1.txt
echo "use_frameworks!" >> tmp2.txt
cat tmp1.txt >> tmp2.txt
cat tmp2.txt > .ios/Podfile
rm tmp1.txt tmp2.txt
}
echo "检查 .ios/Podfile文件状态"
a=$(cat .ios/Podfile)
if [[ $a == use* ]]; then
echo '已经添加use_frameworks, 不再添加'
else
echo '未添加use_frameworks,准备添加'
addFlag
echo "添加use_frameworks 完成"
fi
echo "编译flutter"
flutter build ios --debug
#release下放开下一行注释,注释掉上一行代码
#flutter build ios --release --no-codesign
echo "编译flutter完成"
mkdir $out
cp -r build/ios/Debug-iphoneos/*/*.framework $out
#release下放开下一行注释,注释掉上一行代码
#cp -r build/ios/Release-iphoneos/*/*.framework $out
cp -r build/ios/Debug-iphoneos/App.framework $out
cp -r build/ios/Debug-iphoneos/Flutter.framework $out
#cp -r build/ios/Release-iphoneos/App.framework $out
#cp -r build/ios/Release-iphoneos/Flutter.framework $out
echo "复制framework库到临时文件夹: $out"
libpath='../'
rm -rf "$libpath/ios_frameworks"
mkdir $libpath
cp -r $out $libpath
echo "复制库文件到: $libpath"
然后在终端执行脚本文件
$ sh compile_project.sh
就会将编译所生成的 frameworks 放在 ios_frameworks 文件夹里面。
6. 上传 git
6.1 创建仓库,建立链接。
自己操作就行了。
6.2 修改 flutter_pod 的 .gitignore 文件,注意 这是个隐藏文件。只需要上传编译产物即可,不需要上传 flutter_module 源代码。
代码如下:
# macOS
.DS_Store
# Xcode
build/
DerivedData/
*.xcworkspace/ # 注意:不要忽略整个 .xcworkspace,只忽略内部生成内容
xcuserdata/
# Carthage
Carthage/Build
# 暂时保留 Pods/ 目录的忽略,根据团队规范决定
# Pods/
#ignore flutter_module 工程文件
flutter_module/
EOF
6.3 修改 FlutterPod.podspec 文件
代码如下:
Pod::Spec.new do |s|
s.name = 'FlutterPod'
s.version = '0.1.0'
s.summary = 'A short description of FlutterPod.'
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://gitee.com/TTGF/flutter_pod.git'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'litingting' => '13161138626@163.com' }
s.source = { :git => 'https://gitee.com/TTGF/flutter_pod.git', :tag => s.version.to_s}
s.ios.deployment_target = '10.0'
s.static_framework = true
p = Dir::open ("ios_frameworks")
arr = Array.new
arr.push('ios_frameworks/*.framework')
s.ios.vendored_frameworks = arr
end
不然会出现识别不到 frameworks 的问题。
6.4 上传
上传就完了,无论是使用命令或者可视化软件都行。
6.5 引入使用
在Podfile 文件中 增加
pod 'FlutterPod',:git=>'https://gitee.com/TTGF/flutter_pod.git'
执行 pod install
打开 iOS 项目 如下图所示
就说明成功了,已经将编译产物引入 iOS 工程了。
这样原生开发人员,不需要配置 flutter 开发环境,再也不需要关注是否需要执行 pub get 了,不需要关注flutter 模块报错等各种问题了,每次只需要执行 pod install 即可。
二、原生代码开发
选好「引入方式」之后,原生侧还需要解决三件事:用单引擎还是多引擎、Native 与 Flutter 的路由如何配合、侧滑返回如何与 Flutter 栈协调。本节围绕这三块,给出具体实现和踩坑总结,并顺带说明首次白屏 / Loading、方法通信封装等常见需求的做法。示例与结论均来自实际项目(iOS + Flutter Module)的落地与踩坑。
1. 单引擎方案(SharedEngineFlutterVC)
1.1 概念与适用场景
单引擎:多个 Flutter 页面(或多次进入 Flutter 容器)共用同一个 FlutterEngine。
适合:希望减少内存占用、加快二次进入 Flutter 页面的场景;或同一时刻只展示一个 Flutter 容器的场景。
特点:
-
引擎在 AppDelegate 中创建一次(仅
alloc/init,不立刻run),由第一个使用它的 VC 去run并注册插件。 -
每次打开新的 Flutter 页面时:先
detach掉上一个 FlutterViewController,再对引擎执行destroyContext,然后runWithEntrypoint:initialRoute:切到新路由,最后创建新的 FlutterViewController 并挂到引擎上。 -
因为引擎复用,插件只能在引擎生命周期内注册一次,重复注册会触发
Duplicate plugin key: xxx崩溃(见后文注意点)。
1.2 实现要点(示例)
AppDelegate:只创建引擎,不 run、不注册插件。
- (void)createEngine {
self.engineGroup = [[FlutterEngineGroup alloc] initWithName:@"my_app.engine_group" project:nil];
// 共享引擎:仅创建不 run,由 SharedEngineFlutterVC 首次使用时 run 并注册插件
self.sharedFlutterEngine = [[FlutterEngine alloc] initWithName:@"shared_flutter_engine" project:nil];
}
SharedEngineFlutterVC:首次使用引擎时 run + 注册插件;再次使用时只 destroyContext 再 run,不再调用 GeneratedPluginRegistrant。
- (void)setupSharedEngineAndFlutter {
FlutterEngine *engine = [AppDelegate sharedInstance].sharedFlutterEngine;
self.flutterEngine = engine;
BOOL wasAlreadyRun = (engine.binaryMessenger != nil);
if (wasAlreadyRun) {
[engine destroyContext];
}
if (self.initialRoute.length > 0) {
[engine runWithEntrypoint:nil initialRoute:self.initialRoute];
} else {
[engine run];
}
// 插件只在引擎首次 run 时注册一次,重复注册会触发 Duplicate plugin key 崩溃
if (!wasAlreadyRun) {
[GeneratedPluginRegistrant registerWithRegistry:engine];
}
self.flutterViewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
// ... addChild 等
}
离开页面时只解除 FlutterViewController 与引擎的绑定,不销毁引擎:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
[self detachFlutterViewControllerFromEngine]; // 只 detach,不 destroy 引擎
}
}
1.3 使用示例
从首页进入「单引擎」Flutter 页:
SharedEngineFlutterVC *vc = [[SharedEngineFlutterVC alloc] initWithRoute:@"/detail"
params:params
navigationBarHidden:YES
resultCallback:^(id result) {
[weakSelf showToastIfNeededWithFlutterResult:result];
}];
vc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc animated:YES];
2. 多引擎方案(FlutterWrapperVC)
2.1 概念与适用场景
多引擎:每次从原生 push 一个 Flutter 容器时,就为该次进入创建新的 FlutterEngine(通过 FlutterEngineGroup 的 makeEngineWithOptions:)。
可以理解为:一个业务入口 / 一次 Flutter 容器展示 = 一个引擎。
适合:希望 Flutter 页面之间、或与原生之间状态完全隔离;或不同业务线独立发版、互不影响的场景。
特点:
-
每个 Flutter 容器拥有独立引擎,pop 后引擎可被回收,不存在「复用同一引擎导致的状态残留」。
-
每次都会
run新引擎并注册插件,因此**不存在「插件重复注册」**问题(每个引擎只注册一次)。 -
内存占用相对更高,首次进入某 Flutter 页会有引擎冷启动成本。
2.2 实现要点(示例)
FlutterWrapperVC:通过 FlutterEngineGroup 按需创建引擎,每次 viewDidLoad 若没有引擎则创建并注册插件。
- (void)setupFlutterEngine {
if (self.flutterEngine) return;
AppDelegate *appDelegate = [AppDelegate sharedInstance];
FlutterEngineGroupOptions *options = [[FlutterEngineGroupOptions alloc] init];
options.entrypoint = @"main";
options.libraryURI = nil;
options.initialRoute = self.initialRoute;
// makeEngineWithOptions 返回的引擎已是 running 状态,无需再调 run
self.flutterEngine = [appDelegate.engineGroup makeEngineWithOptions:options];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
}
2.3 使用示例
从首页进入「多引擎」Flutter 页、或从列表点击进入详情:
FlutterWrapperVC *vc = [[FlutterWrapperVC alloc] initWithRoute:@"/detail"
params:params
navigationBarHidden:YES
resultCallback:^(id result) {
[weakSelf showToastIfNeededWithFlutterResult:result];
}];
vc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc animated:YES];
3. 混合路由方案
3.1 两套路径
-
Native → Flutter:通过
initialRoute传的是 MaterialApp 的 routes 名称,例如@"/detail"、@"/commits",对应 Flutter 里routes/onGenerateRoute的 name。 -
Flutter 内部跳转:使用 Fluro 等包管理的 path,例如
series_list/page/series_list_page、page/detail/page/home_commits_list,与 Native 的initialRoute命名可以不一致。
建议在项目里约定清楚:Native 只认 MaterialApp 的 name;Flutter 内部只认 Fluro path,并在文档或常量中写死,避免后续扩展时混用两套命名。
3.2 传参与回调
-
入参:Native push 时把
params通过 parameters_channel(或你项目里的参数通道)在 Flutter 首帧前发给 Flutter,Flutter 在setMethodCallHandler里收initialParams等,再渲染页面。 -
回传结果:Native 在 push 时传入
resultCallback,Flutter 在需要「返回并带数据」时调用InvokeManager.popWithResult(map),Native 在 MethodChannel 的popWithResult分支里收到call.arguments,执行resultCallback(resultValue)并 pop VC。Toast 等 UI 建议在 push 的调用方(例如 HomeViewController)的 resultCallback 里做,而不是在 Flutter 容器类内部写死,这样更符合实际业务(谁 push 谁处理结果)。
3.3 返回方式统一
-
Flutter 根页(栈里只有 1 页)点返回:应走
InvokeManager.popWithResult({'message': '返回原生页面了'})(或你约定的 key),这样 Native 既能 pop 又能收到回调、在 push 处弹 Toast。 -
侧滑返回时不会经过 MethodChannel,因此 Native 在
viewWillDisappear里若发现isMovingFromParentViewController且尚未调用过resultCallback,可补发一次resultCallback(@{@"message": @"返回原生页面了"}),这样点击返回和侧滑返回在「回调 + Toast」上行为一致。
4. 侧滑返回方案(方案 A:系统手势)
4.1 思路与选型
为什么用系统自带的 interactivePopGestureRecognizer,而不是自己写一个 UIScreenEdgePanGestureRecognizer?主要有三点:跟手动画(系统手势自带交互式转场,手指拖动多少、页面跟多少,松手可取消)、与原生体验一致(用户无感知差异)、维护成本低(不用自己算滑动阈值、转场进度,也不易和 Flutter 内部手势冲突)。因此推荐:在 Flutter 容器的 viewDidLoad 里打开系统手势并设置 delegate,让其在 Flutter 页面上也能识别;再通过 MethodChannel 的 add/remove 控制「根页可滑出、多页交给 Flutter」即可。
4.2 Native 实现
在 Flutter 容器的 viewDidLoad 里:
UIGestureRecognizer *popGesture = self.navigationController.interactivePopGestureRecognizer;
popGesture.enabled = YES;
popGesture.delegate = self;
并实现 UIGestureRecognizerDelegate,让系统手势与 Flutter 内部手势同时识别,否则在 Flutter 页面上右滑可能被 Flutter 吃掉:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
通过 Flutter 的 MethodChannel 控制「何时允许系统右滑 pop 整个 VC」:
-
Flutter 调 addGestureRecognizer:表示当前 Flutter 栈只有 1 页,Native 设置
interactivePopGestureRecognizer.enabled = YES,右滑即 pop 整个 Flutter VC。 -
Flutter 调 removeGestureRecognizer:表示栈里多于 1 页,Native 设置
enabled = NO,由 Flutter 自己的 PageRoute 处理滑动返回(只 pop 当前 Flutter 页),避免误把整个 VC 滑掉。
4.3 Flutter 侧协调(SimpleRouteObserver)
用 NavigatorObserver 维护当前栈深度,在 didPush / didPop / didRemove / didReplace 里更新栈数量,并在栈数量变化后根据 pageCount 调用 add/remove,通知 Native 开关系统右滑。核心代码如下(MaterialApp 的 navigatorObservers 需传入该 observer):
class SimpleRouteObserver extends NavigatorObserver {
final List<Route> _routes = [];
int get pageCount => _routes.length;
@override
void didPush(Route route, Route? previousRoute) {
if (previousRoute != null && !_routes.contains(previousRoute)) {
_routes.add(previousRoute);
}
_routes.add(route);
_syncGestureWithNative();
}
@override
void didPop(Route route, Route? previousRoute) {
_routes.remove(route);
_syncGestureWithNative();
}
void _syncGestureWithNative() {
if (pageCount == 1) {
InvokeManager.addGestureRecognizer(); // 仅根页:Native 右滑 = pop 整个 VC
} else {
InvokeManager.removeGestureRecognizer(); // 多页:由 Flutter 自己处理滑动返回
}
}
}
-
栈中只有 1 页:调用
InvokeManager.addGestureRecognizer(),Native 设置interactivePopGestureRecognizer.enabled = YES,右滑即 pop 整个 Flutter VC。 -
栈中多于 1 页:调用
InvokeManager.removeGestureRecognizer(),Native 设置enabled = NO,由 Flutter 的 PageRoute 处理滑动返回。
这样「根页右滑 = 回原生」「多页右滑 = Flutter 内返回」分工明确,且都有跟手动画。
5. 混合开发注意点
5.1 插件只注册一次(单引擎必看)
使用 SharedEngineFlutterVC 时,同一个 FlutterEngine 会被多次 destroyContext 再 run。若每次 run 后都调用 [GeneratedPluginRegistrant registerWithRegistry:engine],会触发 Duplicate plugin key: FluttertoastPlugin(或其他插件)崩溃。
做法:仅在引擎首次 run 时注册插件(用 wasAlreadyRun = (engine.binaryMessenger != nil) 判断,只有 !wasAlreadyRun 时才 registerWithRegistry)。
5.2 MethodChannel 必须在引擎 run 之后
在 run 之前就 setMethodCallHandler 会触发 NSInternalInconsistencyException: Setting a message handler before the FlutterEngine has been run。
因此:先 runWithEntrypoint:initialRoute:(或 run),再 GeneratedPluginRegistrant registerWithRegistry:,再 创建 FlutterViewController 并设置 MethodChannel。
5.3 resultCallback 在「push 处」处理
谁 push 谁处理结果、谁弹 Toast。Flutter 容器内部只负责:收到 popWithResult 时调用 resultCallback(resultValue) 并 pop。
侧滑返回时没有经过 MethodChannel,在 viewWillDisappear 里根据 isMovingFromParentViewController 补发一次 resultCallback,并用标志位避免与 popWithResult 重复调用。
5.4 单引擎下的「状态保持」
当使用共享引擎时,引擎会保持之前 run 过的状态;destroyContext 再 run 会清空 Dart 侧状态。若希望每次进入都是「新页面」体验,当前做法(detach + destroyContext + run 新 route)即可;若未来要做「多 Tab 共享引擎、保留各 Tab 状态」,需要更复杂的状态管理与路由设计,可参考 flutter_boost 等方案。
5.5 AppDelegate 中引擎初始化时机
若在 didFinishLaunchingWithOptions 里就创建并 run 一个共享引擎,可能会影响首屏布局或自定义导航栏(例如约束、safeArea 计算异常)。实践中更稳妥的是:在 AppDelegate 里只 alloc/init 引擎,不 run;第一次 push SharedEngineFlutterVC 时再 run 并注册插件(延时初始化),避免对首屏产生影响。
5.6 文件/产物识别不到(.gitignore)
若采用「iOS 集成 Flutter 编译产物」的方式,务必将 ios_frameworks(或你存放 framework 的目录)纳入版本库,并在 .gitignore 中不要忽略该目录;同时 FlutterPod 的 podspec 里 vendored_frameworks 指向的路径要与实际一致,否则 pod install 后可能识别不到 framework,编译报错。
6. 首次白屏 / Loading
Flutter 容器首次展示时,引擎需要 run、注册插件、创建 FlutterViewController,会有一小段空白期,即「首次白屏」。常见做法有两种:
-
Native 侧盖一层 Loading:在 Flutter 容器的 view 上先加一个
UIActivityIndicatorView或自定义 loading 视图,等 Flutter 首帧渲染完成(可通过 MethodChannel 在 Flutter 里WidgetsBinding.instance.addPostFrameCallback后通知 Native)再移除,或简单用DispatchQueue.main.asyncAfter延迟 0.3~0.5 秒移除。 -
Flutter 侧首屏占位:在 Flutter 的
MaterialApp根布局里,首帧前显示一个居中的CircularProgressIndicator,首帧后再切到实际内容;若 Native 传了「是否首次进入」等标记,也可在 Flutter 里据此决定是否显示占位。
单引擎下二次进入同一 route 时,若做了 destroyContext 再 run,仍会有短暂重建,可按需沿用上述任一方式。
7. 方法通信封装
建议把「Flutter 调 Native」和「Native 调 Flutter」都收敛到统一入口,便于维护和换实现(例如以后换成 EventChannel)。
-
Flutter → Native:封装一个
InvokeManager(或类似命名),内部持有一个MethodChannel,对外提供静态方法如popPage()、popWithResult(result)、addGestureRecognizer()、removeGestureRecognizer()、pushRecommendPage()等,内部统一channel.invokeMethod(methodName, arguments),这样业务侧只关心语义,不关心 channel 名和方法名字符串。 -
Native → Flutter:在 Flutter 容器的
setMethodCallHandler里根据call.method分发到不同处理函数;若需要「Native 主动推数据给 Flutter」,可再增加EventChannel或BasicMessageChannel,由 Native 在合适时机发送,Flutter 监听并更新 UI。
Channel 名称(如 com.example/native_channel、com.example/parameters_channel)建议在 Flutter 和 Native 各用常量定义一份,避免硬编码散落导致不一致。
小结
| 项目 | 单引擎 (SharedEngineFlutterVC) | 多引擎 (FlutterWrapperVC) |
|---|---|---|
| 引擎数量 | 全局一个,复用 | 每次进入新建一个(EngineGroup) |
| 插件注册 | 只在首次 run 时注册,避免重复 | 每个引擎 run 后注册一次即可 |
| 状态 | 需 detach + destroyContext 再 run | 天然隔离,pop 即回收 |
| 内存/启动 | 占用少、二次进入快 | 占用多、每次冷启动 |
| 路由 | initialRoute + Flutter 内 Fluro | 同上 |
| 侧滑返回 | 系统 interactivePopGestureRecognizer + add/remove 控制 | 同上 |
| 回调 | resultCallback 在 push 处处理,侧滑时 viewWillDisappear 补发 | 同上 |
按上述实现后,可以同时支持「单引擎」「多引擎」两种入口,并配合混合路由与侧滑返回方案,在保证体验的前提下避免重复注册、回调遗漏等问题;首次白屏与通信封装按需选用第 6、7 节的思路即可。