flutter-与ios原生交互
前言
flutter
开发也不可能纯flutter
开发,除非只开发一个图片文本浏览的工具,否则一旦用到原生的,那么就需要了解原生
与 flutter
的交互了
交互场景很多,例如:原项目以原生为主,要增加flutter
作为部分功能,如订单模块;项目以flutter
为主,但是使用到了原生的硬件相关,还没有合适的三方,需要通过原生处理
因此 flutter
与原生
之间同时进行开发的情况不在少数,这篇文件主要介绍 flutter
与ios
的交互,android
的也类似,只要大致交互逻辑明白即可
ps
:这里一共介绍两种情况,一种原生为主、flutter为辅
的(初始页面使用原生编写),一种flutter为主、原生为辅
,如果功能差不多看自己选择了,本质差不多
另外实际使用过程中,完全以自己项目为基准,下面两种方案只是初期配置方式不一样,要是自己肯改动,其实原理都一样的(要是两个都看完相信很快理解)
module(原生为主、flutter为辅)
本方案主要采用module
方式配置flutter
,这种情况一般是原生为主、flutter为辅
,即:flutter
编写项目主要应用于某个功能模块,例如:订单、会员、个人信息、活动等,默认从原生开始首页等功能
注
:该方式解耦后,flutter项目和原生就分开了
另外此方式如果想运行完整项目,必须要从 xcode
或者 android studio
开始运行,flutter
中直接运行的仅仅是flutter单个模块
的功能(走的是另外一个默认原生工程
,后面会介绍,是不是很像测试那套😂)
创建flutter module
首先创建一个 flutter
的 module
模块,选择了之后,会发现跟原生编程语言没关系,如下所示(记得项目名字不要大写)
创建完成之后,会发现,创建的默认的app
不一样,android
和ios
的都是.开头的隐藏文件
另外,.ios
隐藏文件夹的 Generated.xcconfig
后面要是报错可能会需要了解他,如下所示
然后就开始配置原生了!
配置原生
创建一个 ios
原生项目,和 flutter
一个目录,如下所示(也可以不一个目录,但更新path
即可),
1、配置Podfile
然后编写 Podfile
文件,如果没有 pod init
初始化,加入内容如下所示
platform :ios, '9.0'
target 'FlutterObjcDemo' do
use_frameworks!
# 导入flutter相关环境
# 路径,../表示前一个目录,./表示当前目录
flutter_application_path = '../flutter_module/'
# 加载环境
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)
# 下面这个也可以加载环境
# eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
end
编辑完成后,然后 pod install
即可
2、设置 bitcode
然后 Build Setting
中 bitcode
设置为 no
注意:到这里就已经设置差不多了,项目也不会报错,但会有一个问题
,那就是项目只能加载flutter
编译好的内容,当flutter
更新文件,却没有重新编译时,那么 xcode
运行的会是flutter
上次编译的缓存,这就很苦恼
3、脚本文件设置
1、拖拽 Generated.xcconfig
文件到项目
执行脚本文件之前,我们往项目中拖拽前面提到的.ios
的Flutter
文件夹,如下所示,不要copy
然后右键移除多余的文件引用(选择Remove References
,不要移动到废纸篓)选择只留下一个,如下所示
注
:这个步骤是自己尝试出来的,看别人的没有提到相对路径无法识别的问题😂
2、添加 run Script
脚本
庆幸的是,flutter
早在tools
中给我们提供了一个xcode_backend.sh
的脚本文件,我们只需要设置好脚本文件,即可在项目运行时自动编译flutter
项目
然后在 Build Phases
中添加一个脚本执行模块
然后,在下面位置添加脚本,且Run Script
的位置,要排在第二位,要在xcode编译之前
,先执行脚本编译flutter
项目,否则运行会出现问题(注意:如果没有此脚本,flutter项目不会编译更新,只会用缓存)
具体脚本内容就如下所示($FLUTTER_ROOT
为flutter
的环境相对目录),复制进去就行,然后编辑运行成功就配置完毕了(前面往项目中拖拽Generated.xcconfig
就是为了这里,有些机器环境就是不识别),运行失败看下面一步
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
备用
:由于环境配置或者其他未知问题,有些电脑可能还是无法识别 $FLUTTER_ROOT
命令,因此会出错,之前提到过Generated.xcconfig
,这个文件里面有这个绝对路径信息,我们将他们复制替换过来就行,如下所示(反正我是同样的配置方式,一台电脑可以,而另一台不可以😂)
"/Users/lishuai/flutter/packages/flutter_tools/bin/xcode_backend.sh" build
"/Users/lishuai/flutter/packages/flutter_tools/bin/xcode_backend.sh" embed
两端交互
这里先说明一下,开启 flutter
模块,实际上就是创建了一个 FlutterViewController
控制器,然后运行,如果想启动运行 Flutter
模块(和默认创建的Flutter
项目一样),那么直接设置 rootViewController
即可,否则就像一个正常的控制器一样,进行push
或者present
注
:虽然下面的案例也是设置rootViewController
,但实际运行过程中不推荐这样使用,这样使用会影响flutter
对ios
端应用声明周期的监听,如果想监听后续会了解到
ios端交互设置
以 Object-c
为例,在 ios
我们需要引入头文件 Flutter.h
,如下所示
#import <Flutter/Flutter.h>
然后创建 FlutterViewController
,通过 FlutterMethodChannel
进行交互
//创建flutter模块寄托的 ViewController
FlutterViewController *vc = [[FlutterViewController alloc] init];
//可以直接设置成根控制器,也可以进行push或者dismiss,这里设置成根控制器
self.window.rootViewController = vc;
//设置 methodChannel 用于和 flutter 通信
//如果原生端组件化开发,那就每个组件创建各自的methodChannel即可,分别对应特定功能
//可以创建多个methodChannel变量,即:起不同的名字,可以分别和flutter不同的功能进行通信
//例如:一个订单页面通信,一个和个人页面通信
self.methodChannel =
[FlutterMethodChannel methodChannelWithName:@"mine/mine_page" binaryMessenger:vc];
//向flutter端以method发送一个消息
//第一个参数为方法名,第二个为参数,参数多的话建议json字符串,两边分别解析
[self.methodChannel invokeMethod:@"one" arguments:nil];
//最后一个参数是发送回调,如果fluter端出现了问题,可以通过回调得到回馈,否则就是成功,一般不会出错
[self.methodChannel invokeMethod:@"one" arguments:nil result:^(id _Nullable result) {}];
//监听flutter发送过来的消息
[self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call,
FlutterResult _Nonnull result) {
//接收到flutter传递过来的消息
//可以看到 call 有 method 和 arguments两个参数,分别为方法名,参数
NSLog(@"ios回调 %@ -- %@", call.method, call.arguments);
//如果交互过程出现了问题,可以调用 result 回调,将内容回调给 flutter端
}];
如果想在运行flutter
时立即传递一个参数,则通过下面方式传递创建 FlutterViewController
即可
FlutterViewController *vc = [[FlutterViewController alloc]
initWithProject:nil initialRoute:@"one" nibName:nil bundle:nil];
flutter端交互设置
如下所示,flutter 中直接声明MethodChannel
对象和ios
端进行通信(android也是一套逻辑),这里没有先引用头文件,相信开发过的会马上明白,报错提示直接自动纠正就引入了 flutter/services
文件
//头文件
import 'package:flutter/services.dart';
//声明 MethodChannel
里面名称需要唯一化:可以功能模块名字/功能名字,也可以像官方提示一样com... 保证不重复即可
final MethodChannel _channel = const MethodChannel("mine/mine_page");
向ios
和android
端发送消息
//和ios端设置一样,第一个参数方法名,第二个为参数
//结果返回一个 Future,如果交互过程出现了问题,可以及时接收反馈结果,和原生端一样
_channel.invokeMethod("testInfo");
_channel.invokeMethod("testInfo", "我是参数");
监听ios
和android
端发来的消息
//设置回调
_channel.setMethodCallHandler((MethodCall call) {
print(call.method);
print(call.arguments);
print("flutter--回调结果");
//这个参数用于反馈给原生端,如果运行出现了问题,那边能通过 callback 监听到回馈
return Future(() => null); //给一个空Future
});
这样两个端的交互问题解决了
module 单独调试
flutter
项目的热更在开发中很节约时间,上面一直用原生运行开发是不现实的,实际上需要整个项目联调的时候才需要从原生端运行
当像单独调试 flutter
的 module
是,很简单,就像我们运行单独的 flutter
项目一样,直接点击运行即可,他会自动以默认的备用空原生项目运行,同时加载 module
内容(还记得前面的 .ios
隐藏文件么,那可不是一个摆设)
就这样,将 module
当成单个 app
运行了,自己可以根据调试内容,设置开始的参数,这式联调的时候去掉测试数据即可
application(flutter为主、原生为辅)
我们新创建一个demo
一般就是这种,一般flutter为主、原生为辅
默认创建的是一个 flutter
项目,如下所示,不多说
然后 flutter
不需要我们配置,原生端呢,其实默认也是配置好的,其他也不需要我们配置
还记得前面讲的 Flutter
模块其实就是开启了一个 FlutterViewController
,其实就是一个控制器,开始的时候,系统会默认将跟控制器设置成 FlutterViewController
,其实和我们的 Module
类似,且 bitcode
和脚本
都设置完毕了,和 module
基本类似,
注
:项目在xcode
运行和flutter
中运行结果一模一样
不信你看这个,刚出创建的项目默认加载 main.storyboard
,其中跟控制器就是 FlutterViewController
是不是感觉,天哪,前面我们做了那么多,干嘛这么费劲,没事,还是有区别的,我们先看看效果,后面对比介绍
如下所示,会发现,他默认继承了 FlutterAppDelegate
,这个平时我们也用不到,modules
可以不用继承,默认的项目继承我们也不用管,改回来也没事
//FlutterAppDelegate的介绍说明,一般用于测试阶段,例如:modules根本不需要,可以根据自己需要重写回调
/**
* `UIApplicationDelegate` subclass for simple apps that want default behavior.
*
* This class implements the following behaviors:
* * Status bar touches are forwarded to the key window's root view
* `FlutterViewController`, in order to trigger scroll to top.
* * Keeps the Flutter connection open in debug mode when the phone screen
* locks.
*
* App delegates for Flutter applications are *not* required to inherit from
* this class. Developers of custom app delegate classes should copy and paste
* code as necessary from FlutterAppDelegate.mm.
*/
@interface AppDelegate : FlutterAppDelegate
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
[self setupFlutter];
// Override point for customization after application launch.
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
flutter和ios交互
交互不多说了,和ios
一样,可以抽离出来,就是因为以 flutter
为主,导致目前只能写到 AppDelegate
中了,可以统一放到一起整理起来
- (void)setupFlutter {
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
_methodChannel = [FlutterMethodChannel methodChannelWithName:@"one_page" binaryMessenger:(FlutterViewController *)vc];
[_methodChannel invokeMethod:@"one" arguments:nil];
//监听退出
[_methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"ios回调 %@ -- %@", call.method, call.arguments);
}];
}
两种方式区别,以及推荐场景
区别
通过 module
创建的项目:
flutter
的模块可以单独运行,不受原生端代码的影响,相对比较独立,当然要通过 flutter
端运行(一般在 android studio),需要整体运行的时候,通过原生端启动即可;
特点
:flutter
端调试方便,减少了原生项目的编译时间,仍然能热更,减少了原生项目的影响,但测试容易遗漏与原生端的沟通问题
通过 application
创建的项目:
flutter
模块不可以单独运行,项目运行的时候会自动先编译flutter
在编译原生
运行,因此每次都要编译两个端,每次都是运行整个项目;
特点
:调试过程中总是针对整体代码,但flutter
仍然可以热更,测试过程中不会遗漏功能,就是每次重新启动都要重新编译整个项目,容易受到原生项目的错误影响
推荐场景
module
比较适用于成熟项目,原生端和 flutter
分离明显,希望他们之间最小影响,更适用于已经用原生开发的项目,后续直接接入module
,开发过程对原生影响也不大
application
比较适合小公司项目或者主要使用 flutter编写的项目,他们之间耦合略严重,但也不是不能分离
可以根据当前场景抉择,当然这只是一部分考量,实际中心偏移也没问题,根据自己项目来选择更快的方式
最后
本篇文章可以作为一个参考,开始你的flutter之旅吧
转载自:https://juejin.cn/post/7087582545516167181