Flutter混合开发-iOS

本文主要针对现有iOS项目想接入flutter,怎么接入flutter,如何进行项目管理,以及Native和flutter之间如何调用,如何调试来讲解的。

一、创建Flutter Module

执行下面的命令创建Flutter Moudle

cd some/path/
flutter create --template module my_flutter

some/path/是你要存放工程的目录,然后创建flutter Module,这一步要注意,不要创建成flutter project项目了,执行命令后,控制台会打印:

Creating project my_flutter... androidx: true
  my_flutter/test/widget_test.dart (created)
  my_flutter/my_flutter.iml (created)
  my_flutter/.gitignore (created)
  my_flutter/.metadata (created)
  my_flutter/pubspec.yaml (created)
  my_flutter/README.md (created)
  my_flutter/lib/main.dart (created)
  my_flutter/my_flutter_android.iml (created)
  my_flutter/.idea/libraries/Flutter_for_Android.xml (created)
  my_flutter/.idea/libraries/Dart_SDK.xml (created)
  my_flutter/.idea/modules.xml (created)
  my_flutter/.idea/workspace.xml (created)
Running "flutter pub get" in my_flutter...                          1.8s
Wrote 12 files.

All done!
Your module code is in my_flutter/lib/main.dart.

创建完成以后my_flutter文件结构如下:

my_flutter/
├── .ios/
│   ├── Runner.xcworkspace
│   └── Flutter/podhelper.rb
├── lib/
│   └── main.dart
├── test/
└── pubspec.yaml

接下来可以在lib中添加代码逻辑,在pubspec.yaml中,添加依赖的packages和plugins。

二、集成方式

1.使用CocoaPods和Flutter SDK集成

这个方案是针对高于Flutter 1.8.4-pre.21版本的SDK的混编方案,如果使用之前的SDK,请查看Upgrading Flutter added to existing iOS Xcode projectAdd Flutter to existing apps

1.1 Podfile 中添加下面配置

flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

../my_flutter是你flutter Moudle存放的目录,这里my_flutter存放在profile的上一级目录,所以这么写。

1.2 Podfile target 中添加install_all_flutter_pods(flutter_application_path)

target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end

这里的MyApp就是对应iOS项目的名称,存放到自己项目对应target中就好了。

1.3 pod install

在Podfile所在目录,执行pod install,如果没问题,会在你的项目中增加以下依赖:

Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing my_flutter (0.0.1)

在执行pod install以后,如果没有增加上面👆的依赖,那么可能是工程有问题。

问题1.Profile中路径添加错误或者my_flutter是Flutter project,不是Flutter Moudle
提示错误如下:

[!] Invalid `Podfile` file: cannot load such file -- ./my_flutter/.ios/Flutter/podhelper.rb.

 #  from /Users/Example/Podfile:10
 #  -------------------------------------------
 #
 >  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
 #
 #  -------------------------------------------

这时候要检查下flutter_application_path是否正确,如果正确,查看下my_flutter是否是Flutter project,可以查看下my_flutter是否包含,.iOS这个文件,注意这是一个隐藏文件,先要在电脑中设置成显示隐藏你文件,再进行查看确认,如果包含则是Moudle,不包含则是project。

问题2.项目签名不对

在my_flutter目录下运行如下命令:

open -a Simulator
flutter build ios

查看能否正确运行,如果提示以下错误,则是证书问题:

It appears that your application still contains the default signing identifier.
Try replacing 'com.example' with your signing id in Xcode:
  open ios/Runner.xcworkspace
Encountered error while building for device.

怎么解决这个问题?

方法一:

  • 1.找到my_flutter/.ios,打开Runner.xcworkspace文件
  • 2.找到Signing & Capabilities,将Signing证书配置正确就可以了(这里要配置Generic iOS Deveice)

方法二:
使用如下命令,忽略签名:

flutter build ios --release --no-codesign

配置成功之后,再次运行flutter build ios,会打印如下信息:

Automatically signing iOS for device deployment using specified development team in Xcode project:
56XB5ELH9A
Running Xcode build...
 ├─Building Dart code...                                    78.1s
 ├─Generating dSYM file...                                   0.1s
 ├─Stripping debug symbols...                                0.0s
 ├─Assembling Flutter resources...                           0.9s
 └─Compiling, linking and signing...                         2.9s
Xcode build done.                                           84.0s
Built

然后再次pod install应该就可以成功了。

1.4 方案优缺点

优点:

  • 1.功能配置简单,方便管理。
  • 2.使用CocoaPods便于集成。

缺点:

  • 1.团队成员都必须配置flutter环境,否则编译不过
  • 2.Native代码和Flutter代码存放在一起,会变得复杂。

2.framework方式接入

2.1生成FrameWork

首先切换到my_flutter所在目录,执行下列命令,生成framework

flutter build ios-framework --output=../Flutter/

命令执行成功后,会在my_flutter同一级目录下,产生Flutter的文件,文件结构如下:

Flutter/
├── Debug/
│   ├── Flutter.framework
│   ├── App.framework
│   ├── FlutterPluginRegistrant.framework (only if you have plugins with iOS platform code)
│   └── example_plugin.framework (each plugin is a separate framework)
├── Profile/
│   ├── Flutter.framework
│   ├── App.framework
│   ├── FlutterPluginRegistrant.framework
│   └── example_plugin.framework
└── Release/
    ├── Flutter.framework
    ├── App.framework
    ├── FlutterPluginRegistrant.framework
    └── example_plugin.framework

2.2配置frameWork路径

在项目中找到这个路径build settings > Build Phases > Link Binary With Libraries
添加$(PROJECT_DIR)/Flutter/Release/Framework Search Paths

配置

2.3嵌入frameWork

在项目中找到这个路径General > Frameworks,Libraries and Embedded Content
app.FrameworkFlutter.FrameWork添加到项目中,就可以使用了。

问题1.Failed to find assets path for "flutter_assets"

Failed to find assets path for "flutter_assets"
[VERBOSE-2:engine.cc(114)] Engine run configuration was invalid.

如果报上面的错误,则在my_flutter中运行以下命令:

flutter clean
flutter build ios

问题2.dyld: Library not loaded: @rpath/Flutter.framework/Flutter

这个问题是说明嵌入frameWork有问题,可以检查一下,Embed Framework和Link Binary With Libraries

Embed Framework

2.4 方案优缺点

优点:

  • 1.团队成员不依赖flutter环境

缺点:

  • 1.打包配置,比较麻烦,都需要手动操作。

3.使用Flutter framework和CocoaPods集成(本地)

3.1生成frameWork

在Flutter v1.13.6之后版本,支持--cocoapods参数,可以使用下面命令。

flutter build ios-framework --cocoapods --output=../Flutter/

生成如下文件结构:

Flutter/
├── Debug/
│   ├── Flutter.podspec
│   ├── App.framework
│   ├── FlutterPluginRegistrant.framework
│   └── example_plugin.framework (each plugin with iOS platform code is a separate framework)
├── Profile/
│   ├── Flutter.podspec
│   ├── App.framework
│   ├── FlutterPluginRegistrant.framework
│   └── example_plugin.framework
└── Release/
    ├── Flutter.podspec
    ├── App.framework
    ├── FlutterPluginRegistrant.framework
    └── example_plugin.framework

3.2配置profile文件

pod 'Flutter', :podspec => '../Flutter/{build_mode}/Flutter.podspec'

3.3 方案优缺点

优点:

  • 1.团队成员不依赖flutter环境
  • 2.可以使用cocoapods集成管理。

缺点:

  • 1.Flutter版本有限制
  • 2.每次需要自己打frmaework

4.使用Flutter framework和CocoaPods集成(远程)

4.1创建一个CocoaPods私有库

在my_flutter同级目录下,创建CocoaPods私有库

$ pod lib create MyFlutterFramework

终端执行代码:

 xingkunkun:FlutterForFW admin$ pod lib create MyFlutterFramework
 Cloning `https://github.com/CocoaPods/pod-template.git` into `MyFlutterFramework `.
Configuring MyFlutter 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?
 >

Running pod install on your new library.

4.2创建一个Flutter Module

  • 1.创建Flutter Module步骤,
flutter create --template module my_flutter
  • 2.构建framework
$ flutter build ios --debug 
 或者 
flutter build ios --release --no-codesign(选择不需要证书)
  • 3.检查.ios目录下
    • 是否有Flutter-->App.framework
    • 是否有Flutter-->engine-->Flutter.framework
.ios目录下
Flutter-->App.framework
Flutter-->engine-->Flutter.framework

4.3将CocoaPods私有库集成到Native项目中

在MyFlutterFramework中创建ios_frameworks文件夹,并将App.frameworkFlutter.framework拷贝进去。

在MyFlutterFramework的podspec文件中,添加以下配置:

  s.static_framework = true
  arr = Array.new
  arr.push('ios_frameworks/*.framework')
  s.ios.vendored_frameworks = arr

之后在MyFlutterFramework的podfile同级目录中执行

$ pod install

在MyApp工程下的podfile文件中添加

platform :ios, '8.0'

target 'MyApp' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MyApp
   pod 'MyFlutterFramework', :path => '../MyFlutterFramework'

end

之后在MyApp的podfile同级目录中执行

$ pod install

这时在MyApp中,就可以找到App.frameworkFlutter.framework

4.4将MyFlutterFramework和my_flutter推送到远程仓库

  • 1.MyFlutterFramework和my_flutter推送到远程仓库
  • 2.修改MyApp工程下的podfile,将pod 'MyFlutterFramework'依赖修改为MyFlutterFramework远程连接。
platform :ios, '8.0'

target 'MyApp' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MyApp
   pod 'MyFlutterFramework', :git=>'https://gitlab.com/MyFlutterFramework.git'

end
    1. 如果MyFlutterFramework中的ios_frameworks不详推送到远程仓库,可以在gitignore文件中添加一下
# 忽略ios_frameworks中文件
ios_frameworks

4.5 方案优缺点

优点:

  • 1.团队成员不依赖flutter环境
  • 2.可以使用cocoapods集成管理。
  • 3.可以使用远程仓库共享和管理项目代码

缺点:

  • 1.每次重新构建,需要移动framework位置,比较繁琐,可以使用脚本解决。

三、Flutter与Native交互

Flutter 官方提供了一种 Platform Channel 的方案,用于 Dart 和平台之间相互通信。

核心原理:

  • Flutter应用通过Platform Channel将传递的数据编码成消息的形式,跨线程发送到该应用所在的宿主(Android或iOS);
  • 宿主接收到Platform Channel的消息后,调用相应平台的API,也就是原生编程语言来执行相应方法;
  • 执行完成后将结果数据通过同样方式原路返回给应用程序的Flutter部分。

Flutter提供了三种不同的Channel:

  • BasicMessageChannel(主要是传递字符串和一些半结构体的数据)
  • MethodChannel(用于传递方法调用)
  • EventChannel(数据流的通信)

下面是使用Platform Channel进行通信的示例:
示例代码

1.Native app主动与Flutter交互

交互主要分为三步:

  • 1.flutter注册MethodChannel
  • 2.flutter MethodChannel监听native消息
  • 3.native通过MethodChannel发送消息

Dart代码

class HomePage extends StatefulWidget {
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  String title = 'Flutter to Native';
  Color backGroundColor = Colors.red;

// 注册一个通知
  static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');

  _HomePageState(){
    //Native调用Dart方法
    methodChannel.setMethodCallHandler((MethodCall call){
      if(call.method  == "NativeToFlutter"){
        setState(() {
          title = call.arguments;
          backGroundColor = Colors.yellow;
        });
      }
      return Future<dynamic>.value();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: backGroundColor,
      body: Center(
        child: GestureDetector(
          behavior: HitTestBehavior.opaque,
          child: new Text(title),
          onTap: (){
            _iOSPushToVC();
          },
        ),
      ),
    );
  }
}

Native代码

#import "ViewController.h"
#import <Flutter/Flutter.h>

#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)

@interface ViewController ()

@property (nonatomic, strong) FlutterMethodChannel *messageChannel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(pressOn) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"加载Flutter" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake((SCREEN_WIDTH-100)/2, 100, 100, 60);
    [self.view addSubview:button];

}

- (void)pressOn
{
    FlutterViewController *flutterViewController =[FlutterViewController new];
    //设置路由参数
    [flutterViewController setInitialRoute:@"route"];
    
    NSString *channelName = @"com.pages.your/native_get";// 要与main.dart中一致
    _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
    [self nativeToFlutter];
    [self presentViewController:flutterViewController animated:false completion:nil];
}

- (void)nativeToFlutter
{
    sleep(5);
    [_messageChannel invokeMethod:@"NativeToFlutter" arguments:@"NativeToFlutter"];
}

@end

2.Flutter主动与Native app交互

交互主要分为以下几步:

  • 1.Native创建MethodChannel。
  • 2.Native添加HandleBlcok。
  • 3.Flutter发送消息。

Dart代码

class HomePage extends StatefulWidget {
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  String title = 'Flutter to Native';
  Color backGroundColor = Colors.red;

// 注册一个通知
  static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');

  //Dart调用Native方法,并接收返回值。
  _iOSPushToVC() async {
    title = await methodChannel.invokeMethod('FlutterToNative');
    setState(() {
      backGroundColor = Colors.green;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: backGroundColor,
          body: Center(
              child: GestureDetector(
                behavior: HitTestBehavior.opaque,
                child: new Text(title),
                onTap: (){
                  _iOSPushToVC();
                },
              ),
          ),
        );
  }
}

Native代码

#import "ViewController.h"
#import <Flutter/Flutter.h>

#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)

@interface ViewController ()

@property (nonatomic, strong) FlutterMethodChannel *messageChannel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(pressOn) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"加载Flutter" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake((SCREEN_WIDTH-100)/2, 100, 100, 60);
    [self.view addSubview:button];

}

- (void)pressOn
{
    FlutterViewController *flutterViewController =[FlutterViewController new];
    //设置路由参数
    [flutterViewController setInitialRoute:@"route"];
    
    NSString *channelName = @"com.pages.your/native_get";// 要与main.dart中一致
    _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
    

    __weak typeof(self) weakSelf = self;
    [_messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result)
    {
        __strong typeof(self) strongSelf = weakSelf;
        if ([call.method isEqualToString:@"FlutterToNative"]) {
          if (result) {
              result(@"NativeBack");
          }
        }
    }];
    
    [self presentViewController:flutterViewController animated:false completion:nil];
}

@end

四、app调试

混合开发的时候,需要在XCode调试代码,在调试的过程中,怎么调试Dart代码呢?或者能不能使用热加载?

1.调试Dart代码

在混合开发过程中,在iOS项目中,我们如何调试dart代码呢?

  • 1.关闭我们的app
  • 2.点击Android Studio工具栏上的Flutter Attach按钮
    • 点击之后会提示Waiting for a connection from Flutter on iPhone 11 Pro...
  • 3.启动我们的app
    • 启动app之后,会提示Syncing files to device iPhone 11 Pro...

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

2.热加载

  • 1.关闭我们的app
  • 2.在terminal中运行 flutter attach命令。
$ flutter attach
Waiting for a connection from Flutter on iPhone 11 Pro Max...

注意,这里如果提示有多个设备,如下所示:

More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.

我的 iPhone       • 00008030-000445611146802E            • ios • iOS 13.3
iPhone 11 Pro Max • 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-2 (simulator)

可以使用以下命令:

 flutter attach -d 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 //67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9是设备对应的id
  • 3.启动app,启动之后会有如下提示,就代表成功了。
Syncing files to device iPhone 11 Pro Max...                            
 4,196ms (!)                                       

🔥  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone 11 Pro Max is available at: http://127.0.0.1:62889/T9DjblAu03w=/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

接下来就可以在terminal调试了:

r : 热加载;
R : 热重启;
h : 获取帮助;
d : 断开连接;
q : 退出;

参考资料:

Integrate a Flutter module into your iOS project

Upgrading Flutter added to existing iOS Xcode project

Add Flutter to existing apps

flutter集成进iOS工程

闲鱼flutter-boot介绍

优雅的 Flutter 组件化 混编方案

Flutter和原生iOS交互

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

深入理解Flutter Platform Channel

Flutter混合开发二-FlutterBoost使用介绍

如何用 Flutter 实现混合开发?

深入理解Flutter的Platform Channel机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容