iOS 工程集成 flutter module

122 阅读13分钟

前言:

在原有的iOS工程中,集成flutter module也是一种常见的开发模式。可以保证之前业务的稳定性,新的模块完全可以使用flutter来实现。当然中间也会涉及到很多的问题,比如 引入flutter 方式,多引擎还是单引擎,iOS和flutter怎么交互等等。

一、引入方式

第一种方案: Flutter 官方已经给出的混编方案:

github.com/flutter/flu…

文档里写的比较清楚了的,这里就不再多做赘述,大意就是说现在我们只需要在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的编译产物

flutterframework 的形式通过 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.

创建完了之后如下图所示:

WX20250909-120216@2x.png

3. 在FlutterPod目录下创建 Flutter Module模块

创建完后如下图所示

WX20250909-133545@2x.png

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文件夹,里面装的就是需要的编译产物,如下图所示。

WX20250909-134613@2x.png

需要 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 文件夹里面。

WX20250909-140210@2x.png

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 项目 如下图所示

WX20250909-142249.png

就说明成功了,已经将编译产物引入 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 + 注册插件;再次使用时只 destroyContextrun不再调用 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(通过 FlutterEngineGroupmakeEngineWithOptions:)。

可以理解为:一个业务入口 / 一次 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_pagepage/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 开关系统右滑。核心代码如下(MaterialAppnavigatorObservers 需传入该 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 会被多次 destroyContextrun。若每次 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 过的状态;destroyContextrun 会清空 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 时,若做了 destroyContextrun,仍会有短暂重建,可按需沿用上述任一方式。


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」,可再增加 EventChannelBasicMessageChannel,由 Native 在合适时机发送,Flutter 监听并更新 UI。

Channel 名称(如 com.example/native_channelcom.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 节的思路即可。

iOS工程

flutter工程