1. MethodChannel 简介
MethodChannel 是 iOS 与 Flutter 通信的一种方式, 是一种双向通信, 主要用来互相调用方法, 同时可以传递参数, 也可以接收返回的数据, 是一次性的通信, 一旦通信结束, 想要再次通信, 需要重新调用对应的方法.
本文正好也解决一下 iOS 嵌入 Flutter 这篇文章中, 加载 flutter 页面卡顿的情况. 其实也比较简单, 就是 FlutterEngine 和 FlutterViewController 都用单例来管理, 提前初始化, 在程序中只保存一份, 也不需要销毁. 这样做有两个好处, 一个是解决了第一次加载卡顿问题, 另一个是内存泄漏问题.
MethodChannel 使用类似 与 JS 交互用的 WebViewJavascriptBridge.
2. MethodChannel 使用场景
假设项目是 iOS 原生嵌入 Flutter 这么一个场景, iOS 原生嵌入 Flutter 的配置流程可以看篇文章 iOS 嵌入 Flutter
2.1 iOS 调用 flutter
flutter_module 中根据 iOS 调用不同的 method, 展示不同的页面. flutter_module 中监听到 iOS 原生调用之后, 加载页面, 然后再给 iOS 端返回一些数据.
2.2 flutter 调用 iOS
点击 flutter_module 页面中的按钮, 调用 iOS 的method, iOS 端收到调用后 dismiss 掉当前页面, 再通过 block 给 flutter_module 页面回调一引起数据.
注意: 两端各自收到调用之后的回调数据, 可以有, 也可以没有, 都有对应的 API, 根据自己具体需求确定, 因为本文是在讲述用法, 就全写上了.
2.3 场景效果
- 操作效果
-
控制台输出
3. 使用方法
3.0 Flutter 引擎初始化
首先要说的是, 这只是测试 Demo, 并没有封装成单例, 但是也把 FlutterEngine 和 FlutterViewController 做为 ViewController 的属性, 在 viewDidLoad 中初始化 FlutterEngine 和FlutterViewController, 并启动 FlutterEngine. 效果与单例类似, 实际开发中, 大家可以根据自己的情况来灵活运用, 理解万岁 😄😄😄.
完整代码放在最后
@interface ViewController ()
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@property (nonatomic, strong) FlutterViewController *flutterVC;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
// 全屏时 flutter 页面就需要写回调来告诉原生 dismiss
vc.modalPresentationStyle = UIModalPresentationFullScreen;
self.flutterVC = vc;
}
- (FlutterEngine *)flutterEngine {
if (!_flutterEngine) {
FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"];
// FlutterEngine 需要 run 起来, 有可能失败.
// 所以只有 run 成功的时候才赋值.
if ([flutterEngine run]) {
_flutterEngine = flutterEngine;
}
}
return _flutterEngine;
}
@end
3.1 MethodChannel 初始化
3.1.1 iOS 初始化
此处的 FlutterMethodChannel 也可以做为属性记录下来, 大家可以自己尝试一下.
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];
3.1.2 flutter 初始化
final MethodChannel _channel_1 = const MethodChannel('channel_1');
3.2 iOS 调用 flutter
3.2.1 iOS 中的调用
iOS 中对 flutter 的调用, 对应的是 flutter 中的监听. 当一个 method 调用过去的时候, flutter 根据 method name 做出不同的响应, 完成不同的任务, 在完成任务后, 还可以返回给 iOS 端数据.
参数1:NSString类型, 是 method name, 用来表示我们要做什么事情参数2:id类型, 是传递给 flutter 端的参数,参数3: FlutterResult 类型, 是一个 block. 用来接收 flutter 传回 iOS 的数据.FlutterResult: 参数 result 是 id 类型.typedef void (^FlutterResult)(id _Nullable result);
没有回调的
```
[methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数"];
```
有回调的
```
// 调用 flutter
// Method : Method 名称
// arguments : 传递给 flutter 的数据
// result : 是 flutter 回调回来的数据
[methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id _Nullable result) {
NSLog(@"ios: %@", result);
}];
```
3.2.2 flutter 中的监听
handler: 匿名函数,- call:
MethodCall类型参数call, 具体在下面单独讲解. - 返回值: 需要给 iOS 回调数据就给一个返回值, 不需要就不给返回值, 但是要加上
async, 具体位置看代码.
- call:
call: 有两个属性,method和argumentsmethod:String类型, iOS 调用 flutter 时传递的method name. 可以判断method来完成不同的任务.arguments:dynamic类型, iOS 调用 flutter 时传递的参数, 根据对应method的参数约定类型解析即可.
没有给 iOS 回传数据的
_channel_1.setMethodCallHandler((call) async {
setState(() {
// 此处一个 channel 可以调用很多 method,
if (call.method == 'two') {
pageIndex = call.method;
}
});
});
有给 iOS 回传数据的
// 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调
_channel_1.setMethodCallHandler((call) {
setState(() {
// 此处一个 channel 可以调用很多 method,
// if (call.method == 'two') {
pageIndex = call.method;
// }
});
return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111');
});
3.3 flutter 调用 iOS
3.3.1 flutter 中的调用
此处涉及到 async 所以我把按钮的点击方法参数onPressed也带过来了,
参数1:String类型,method name, 对应 iOS 端监听中 call 的method属性, 以此判断需要完成的任务.参数2: 动态类型, 调用method同时传递给iOS中的参数, 对应 iOS 端监听中 call 的arguments属性.
不接收 iOS 回传数据
onPressed: () {
_channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');
},
接收 iOS 回传数据
onPressed: () async {
var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');
print(result.toString());
},
3.3.2 iOS 中的监听
- Handler: block, 两个参数
call和result, 具体下面讲. call:FlutterMethodCall类型, 两个属性method: NSString 类型, method name, 对应 flutter 调用时的method, 以此来判断需要执行的任务.arguments:method对应的参数. id 类型.
result:FlutterResult类型, 用来给 flutter 返回数据, 是一个 block, 需要返回就调用, 不需要就不调用, 参数是 id 类型.typedef void (^FlutterResult)(id _Nullable result);
不给 flutter 调用返回数据的
// flutter 调用 ios
// call : method 名称和 参数
// method -> string
// arguments -> id
// result : 给 flutter 回调的数据, 参数是 id 类型
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqual:@"dismiss"]) {
NSLog(@"%@", call.arguments);
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
给 flutter 调用返回数据的
// flutter 调用 ios
// call : method 名称和 参数
// method -> string
// arguments -> id
// result : 给 flutter 回调的数据, 参数是 id 类型
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqual:@"dismiss"]) {
NSLog(@"%@", call.arguments);
[self dismissViewControllerAnimated:YES completion:nil];
}
result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
}];
3.4 数据传递建议
二者相互传递数据的类型建议用 json 格式字符串, 这样方便封装数据解析, 在传递数据前, 将模型数据转换成 json字符串; 在收到数据之后, 解析成模型, 这些操作大家肯定也都熟悉的很, 可以完美对接我们现在的工具链.
4. 总结
MethodChannel 通信特点:
- 用于二者互相调用, 可带参数, 可接收返回值.
- 双向通信
- 一次性通信
iOS端调用要在主线程
5. 完整代码
5.1 iOS 原生代码
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@property (nonatomic, strong) FlutterViewController *flutterVC;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
// 全屏时 flutter 页面就需要写回调来告诉原生 dismiss
vc.modalPresentationStyle = UIModalPresentationFullScreen;
self.flutterVC = vc;
}
- (FlutterEngine *)flutterEngine {
if (!_flutterEngine) {
FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"];
// FlutterEngine 需要 run 起来, 有可能失败.
// 所以只有 run 成功的时候才赋值.
if ([flutterEngine run]) {
_flutterEngine = flutterEngine;
}
}
return _flutterEngine;
}
// one page
- (IBAction)pushFlutter:(id)sender {
// 初始化 FlutterMethodChannel
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];
// 调用 flutter
// Method : Method 名称
// arguments : 传递给 flutter 的数据
// result : 是 flutter 回调回来的数据
[methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id _Nullable result) {
NSLog(@"ios: %@", result);
}];
[self presentViewController:self.flutterVC animated:YES completion:nil];
// flutter 调用 ios
// call : method 名称和 参数
// method -> string
// arguments -> id
// result : 给 flutter 回调的数据, 参数是 id 类型
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqual:@"dismiss"]) {
NSLog(@"%@", call.arguments);
[self dismissViewControllerAnimated:YES completion:nil];
}
result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
}];
}
// two page
- (IBAction)pushFlutter_2:(id)sender {
// 传递参数
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_2" binaryMessenger:self.flutterVC.binaryMessenger];
[methodChannel invokeMethod:@"two" arguments:@"来自 ios channel_2 的参数" result:^(id _Nullable result) {
NSLog(@"ios: %@", result);
}];
[self presentViewController:self.flutterVC animated:YES completion:nil];
// flutter 调用 ios 的响应处理 并通过 result 回调返回给 flutter 一个数据
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqual:@"dismiss"]) {
NSLog(@"%@", call.arguments);
[self dismissViewControllerAnimated:YES completion:nil];
}
result(@"channel_2: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
}];
}
@end
5.2 Flutter 代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String pageIndex = '';
final MethodChannel _channel_1 = const MethodChannel('channel_1');
final MethodChannel _channel_2 = const MethodChannel('channel_2');
@override
void initState() {
// TODO: implement initState
super.initState();
// 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调
_channel_1.setMethodCallHandler((call) {
setState(() {
// 此处一个 channel 可以调用很多 method,
if (call.method == 'one') {
print(call.arguments);
pageIndex = call.method;
}
});
return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111');
});
// 接收 ios _channel_2 的调用, 并返回给 ios 一个数据回调
_channel_2.setMethodCallHandler((call) {
setState(() {
// 此处一个 channel 可以调用很多 method,
if (call.method == 'two') {
print(call.arguments);
pageIndex = call.method;
}
});
return Future.value('_channel_2: flutter 响应 ios 后, 回调给 ios 的数据 222');
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Module',
theme: ThemeData(
primarySwatch: Colors.blue
),
home: _rootPage(pageIndex),
);
}
Widget _rootPage(String pageIndex) {
switch(pageIndex) {
case 'one':
return Scaffold(
appBar: AppBar(
title: Text(pageIndex),
),
body: Center(
child: ElevatedButton(
child: Text(pageIndex),
onPressed: () async {
var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');
print(result);
},
),
),
);
case 'two':
return Scaffold(
appBar: AppBar(
title: Text(pageIndex),
),
body: Center(
child: ElevatedButton(
child: Text(pageIndex),
onPressed: () async {
var result = await _channel_2.invokeMethod('dismiss', 'two: 来自 flutter 的参数 hello two');
print(result);
},
),
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('hello flutter'),
),
body: const Center(
child: Text('hello flutter'),
),
);
}
}