本文首发于微信公众号「Android开发之旅」,欢迎关注 ,获取更多技术干货

前言

前一篇文章讲解了 Android 原生工程如何集成Flutter项目的具体过程,Flutter混合开发(一):Android项目集成Flutter模块详细指南 ,本篇将带着大家来一起学习原生iOS项目如何集成Flutter。

因为每个版本的集成逻辑都是有差别的,所以这里交代下本篇文章的集成版本:

  1. Flutter channel devv1.10.16
  2. dart v2.7.0
  3. Xcode v11.2
  4. cocoapds 1.8.4

创建Flutter module

假如iOS项目的路径是这样的:flutter/flutter_hybrid/iOS Project,那么我们需要在iOS Project上一层目录flutter_hybrid中创建Flutter module。

  1. cd flutter/flutter_hybrid/
  2. flutter create -t module flutter_module

输入后控制台打印如下:

  1. $ flutter create -t module flutter_module
  2. Creating project flutter_module...
  3. flutter_module/test/widget_test.dart (created)
  4. flutter_module/flutter_module.iml (created)
  5. flutter_module/.gitignore (created)
  6. flutter_module/.metadata (created)
  7. flutter_module/pubspec.yaml (created)
  8. flutter_module/README.md (created)
  9. flutter_module/lib/main.dart (created)
  10. flutter_module/flutter_module_android.iml (created)
  11. flutter_module/.idea/libraries/Flutter_for_Android.xml (created)
  12. flutter_module/.idea/libraries/Dart_SDK.xml (created)
  13. flutter_module/.idea/modules.xml (created)
  14. flutter_module/.idea/workspace.xml (created)
  15. Running "flutter pub get" in flutter_module... 1.2s
  16. Wrote 12 files.
  17. All done!
  18. Your module code is in flutter_module/lib/main.dart.

看到All done就表示我们项目创建好了。整个module目录和原生Flutter基本一样,主要就是Android、iOS的宿主工程和lib目录以及pubspec.yaml文件。

添加Flutter module依赖

为iOS项目添加依赖需要使用CocoaPods,如果你还没有用到CocoaPods,可以参考https://cocoapods.org/上面的说明来安装CocoaPods。

如果你的项目之前没有使用过cocoapods,那么需要进行初始化生成podfile文件,进入iOS项目的根目录执行:

  1. pod init

然后打开podfile文件,进行配置:

  1. # 配置
  2. flutter_application_path = '../flutter_module/'
  3. load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  4. target 'iOSFlutterHybrid' do
  5. # Comment the next line if you don't want to use dynamic frameworks
  6. use_frameworks!
  7. # Pods for iOSFlutterHybrid
  8. # 配置
  9. install_all_flutter_pods(flutter_application_path)
  10. target 'iOSFlutterHybridTests' do
  11. inherit! :search_paths
  12. # Pods for testing
  13. end
  14. target 'iOSFlutterHybridUITests' do
  15. # Pods for testing
  16. end

配置添加好后在项目根目录运行以下命令进行安装:

  1. pod install

控制台输出:

  1. Analyzing dependencies
  2. Downloading dependencies
  3. Installing Flutter (1.0.0)
  4. Installing FlutterPluginRegistrant (0.0.1)
  5. Installing flutter_module (0.0.1)
  6. Generating Pods project
  7. Integrating client project
  8. [!] Please close any current Xcode sessions and use `iOSFlutterHybrid.xcworkspace` for this project from now on.
  9. Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
  10. [!] Automatically assigning platform `iOS` with version `13.2` on target `iOSFlutterHybrid` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.

这里我们看到有三个依赖安装完成了。并提醒我们关闭当前项目,在根目录下面使用iOSFlutterHybrid.xcworkspace来打开运行项目。这里可能很多人在执行命令的时候会发现提示0个依赖完成。这里有可能是你的Xcode版本的问题。因为Flutter要求最低版本是10.2及以上。

当在flutter_module/pubspec.yaml中添加一个Flutter插件时,需要在flutter_module目录下运行:

  1. flutter packages get

来刷新podhelper.rb脚本中的插件列表,然后在iOS目录下运行:

  1. pod install

这样podhelper.rb脚本才能确保添加的插件和Flutter.framework能够添加到iOS项目中。

目前Flutter还不支持Bitcode,所以集成了Flutter的iOS项目需要禁用Bitcode。

在以下路径下找到Bitcode并禁用:

  1. Build Settings->Build Options->Enable Bitcode

Flutter混合开发(二):iOS项目集成Flutter模块详细指南 - 图1

flutter以前的版本是需要添加build phase以构建Dart代码,但是最新的版本已经不需要添加了,可以自动构建。

调用Flutter module

Flutter为我们提供了两种调用方式:FlutterViewController和FlutterEngine,FlutterEngine在使用的时候会有一些问题,将在下文进行说明。

FlutterViewController方式:

我们打开ViewController.m文件,在里面添加一个加载flutter页面的方法并且添加一个按钮看来调用:

  1. #import "ViewController.h"
  2. #import <Flutter/Flutter.h>
  3. #import "AppDelegate.h"
  4. @interface ViewController ()
  5. @end
  6. @implementation ViewController
  7. - (void)viewDidLoad {
  8. [super viewDidLoad];
  9. UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  10. [button addTarget:self action:@selector(handleButtonAction) forControlEvents:UIControlEventTouchUpInside];
  11. [button setTitle:@"加载Flutter" forState:UIControlStateNormal];
  12. [button setBackgroundColor:[UIColor blueColor]];
  13. button.frame = CGRectMake(100, 100, 160, 60);
  14. [self.view addSubview:button];
  15. }
  16. - (void)handleButtonAction{
  17. FlutterViewController *flutterViewController =[FlutterViewController new];
  18. //设置路由参数
  19. [flutterViewController setInitialRoute:@"route2"];
  20. [self presentViewController:flutterViewController animated:false completion:nil];
  21. }
  22. @end

当我们运行项目点击加载Flutetr按钮时,将会调用Flutter页面。和Android项目集成一样,这里的setInitialRoute可以设置一个json数组来传递需要交互的参数。并在Flutter中使用window.defaultRouteName来获取传递的参数。

FlutterEngine方式:

我们需要在AppDelegate中对FlutterEngine进行初始化。打开AppDelegate.h文件:

  1. #import <UIKit/UIKit.h>
  2. #import <Flutter/Flutter.h>
  3. @interface AppDelegate : FlutterAppDelegate
  4. @property (nonatomic,strong) FlutterEngine *flutterEngine;
  5. @end

在打开AppDelegate.m文件:

  1. // 如果你需要用到Flutter插件时
  2. #import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
  3. #include "AppDelegate.h"
  4. @implementation AppDelegate
  5. - (BOOL)application:(UIApplication *)application
  6. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  7. self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  8. [self.flutterEngine runWithEntrypoint:nil];
  9. [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; //如果你需要用到Flutter插件时
  10. return [super application:application didFinishLaunchingWithOptions:launchOptions];
  11. }
  12. @end

然后在ViewController.m文件定义的handleButtonAction中调用:

  1. - (void)handleButtonAction{
  2. FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
  3. FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
  4. [flutterViewController setInitialRoute:@"route2"];
  5. [self presentViewController:flutterViewController animated:false completion:nil];
  6. }

当我们运行项目点击加载Flutter按钮时,将会调用Flutter页面。前文讲到使用FlutterEngine会有问题,就是我们setInitialRoute传递的参数在flutter中永远获取到的都是 “/” ,这个是Fltter SDK的一个Bug,所以如果必须依赖setInitialRoute,还是使用FlutterViewController的形式来加载Flutter模块。

热重启/重新加载

大家在写纯Flutter应用的时候,知道是有热重启/重新加载功能的,但是在做混合开发的过程中,你会发现热重启/重新加载功能失效了。那么如何在混合开发中开启热重启/重新加载功能呢?

  • 首先接入我们的设备或者模拟器
  • 将我们的App关闭,退出后台,在terminal中运行 flutter attach命令
  1. $ flutter attach
  2. Waiting for a connection from Flutter on Android SDK built for x86...

复制代码此时就在等待设备的连接。这里要注意的是,如果电脑连接了多台设备需要使用 -d 命令来指定一台设备,参数为设备的id。

  1. flutter attach -d '你的设备id'

然后启动我们的应用会看到控制台输出:

  1. Done.
  2. Syncing files to device Android SDK built for x86... 1,393ms
  3. 🔥 To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
  4. An Observatory debugger and profiler on Android SDK built for x86 is available at: http://127.0.0.1:59354/zRsDBfpesrk=/
  5. For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

这样就表示我们连接成功了。在输出的日志中也告诉了我们如何使用热重启/重新加载功能。

在Terminal中输入以下命令:

  1. r : 热加载;
  2. R : 热重启;
  3. h : 获取帮助;
  4. d : 断开连接;
  5. q : 退出;

这里的的 d 和 q 的命令都有退出调试,区别在于 d 命令只是单纯的断开而 q 命令会将应用退到后台。

调试Dart代码

同样在混合开发过程中我们如何调试dart代码呢?

  • 关闭我们的应用
  • 点击Android Studio工具栏上的Flutter Attach按钮(需要安装Flutter与Dart插件)

Flutter混合开发(二):iOS项目集成Flutter模块详细指南 - 图2

  • 启动我们的应用

接下来就可以像调试普通Flutter项目一样来调试混合开发模式下的Dart代码了。

总结

本文主要是讲解了iOS集成Flutter项目的步骤,其中也遇到了一些问题,由于我的Xcode版本较低,在集成的过程中iOS项目的依赖一直失败。最后才发现是Xcode的版本问题。这里花费了很多时间去排查问题,所以大家在集成的过程中有问题可以关注公众号加我微信,给我留言,我会帮助大家解决问题。

全部Demo源码已经上传到后台,关注公众号回复「混合开发」即可获得下载链接。

如果你觉得文章还不错,请大家点赞分享下,你的肯定是对我最大的鼓励和支持。