iOS和Flutter通信初探

Flutter 使用了一套灵活的系统来调用平台特定的APIs,无论是iOS还是安卓。
Flutter支持平台特定API不是依赖于生成代码的方式,而是通过灵活的消息传递。

  1. 应用的Flutter部分发送消息到它的宿主(应用的iOS或者Android部分)通过,platform channel.
  2. 应用宿主在platform channel上面监听并且接受消息。然后调用任意数量的特定平台的API(通过原生语言),然后发送一个回调给到Flutter 部分。
    注意 如果您需要使用Java/Kotlin/Objective-C或Swift中的平台api或库,本文将介绍如何使用平台通道机制。但是,通过检查defaultTargetPlatform属性,您还可以在Flutter应用程序中编写特定于平台的Dart代码。平台适配列出了一些特定于平台的适配,这些适配会在框架中自动执行Flutter。
    平台渠道的架构概述
    使用平台通道在客户端(UI)和主机(平台)之间传递消息,如下图所示:

image

消息和响应是异步传递的,以确保用户界面保持响应性。

尽管Flutter是异步地向Dart发送消息,但无论何时调用channel方法,都必须在平台的主线程上调用该方法。

在客户端,MethodChannel (API)允许发送与方法调用对应的消息。在平台端,Android上的MethodChannel (API)和iOS上的FlutterMethodChannel (API)支持接收方法调用和发送结果。这些类允许您用很少的“样板”代码开发平台插件。

平台通道数据类型支持和编解码器

标准平台通道使用标准消息编解码器,该编解码器支持对简单的json类值(如布尔值、数字、字符串、字节缓冲区以及这些值的列表和映射)的高效二进制序列化(有关详细信息,请参阅StandardMessageCodec)。当您发送和接收值时,这些值与消息之间的序列化和反序列化将自动发生。

下表显示了在平台端接收Dart值的方式,反之亦然。

image

好了,原理介绍完了,下面一起来看一下具体是如何跑起来的。参照官方文档做了一个在iOS平台获取手机电量的例子。这个例子会示范如何调用平台特定的API来检索以及展示当前设备的电量。它将会使用Android BatteryManager API,和iOS device.batteryLevel API,通过一个单独的平台方法,getBatteryLevel()。

创建一个flutter工程
使用如下命令创建:

1
flutter create platform_channel

或者你可以通过设置特定的语言来指定默认的模板:

1
flutter create -i objc -a java platform_channel

使用你安装的编译器来打开创建好的工程,我使用的是idea,然后在你工程中的main.dart里面添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class _MyHomePageState extends State<MyHomePage> {
// 创建client和host通信的通道
static const platform = const MethodChannel('charles.flutter.dev/battery');
String _batteryLevel = 'Unknown battery level.';
// 获取电量方法
Future<void> _getBatteryLevel()async{
String batteryLevel;
try{
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
}on PlatformException catch(e){
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
}

简单解释一下:首先创建了platform 实例,这个就是通信的通道。
client 和 host之间通过通道名称进行连接。单个应用中使用的所有通道名称必须是唯一的,给通道名字添加域前缀,例如:charles.flutter.dev/battery。
_getBatteryLevel()方法就是异步的获取当前设备电量的方法。这个方法中最核心的一句:

1
final int result = await platform.invokeMethod('getBatteryLevel');

就是通过通道,去调用host端的getBatteryLevel方法,并且把获取到的值但会到client端。

展示电量的部分就不介绍了。大家可以看下文附的Demo.

host端的话,只介绍iOS端了,安卓类似。有兴趣的朋友可以自己看看。
从flutter 项目目录下找到 ios/Runner.xcworkspace 双击打开。
在Appdelegate 里面添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:controller];

__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// Note: this method is invoked on the UI thread.
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];

if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (int)getBatteryLevel {
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
@end

解释一下:这段代码也是先创建一个通道 batteryChannel,可以看到这里使用的channel name 和上文中提到的 client 端里面的完全一致。同时设置了消息的发送者。

getBatteryLevel 就是iOS获取电量的方法,不做过多解释,到此为止,你可以运行platform_channel flutter项目来看一下是否可以获取到当前手机的电量。

效果如下:

image

以上介绍的就是利用MethodChannel从Flutter端调用平台端函数的方法。接下来我们介绍一下如何从平台端调用Flutter端的函数,我们需要在平台端代码部分编写如下方法:

1
2
3
4
5
- (void)invokeFlutterMethod:(FlutterMethodChannel *)channel {
[channel invokeMethod:@"flutterMethod" arguments:nil result:^(id _Nullable result) {
NSLog(@"result is %@",result);
}];
}

在调用返回电量结果后,调用该方法,为了效果明显我们延迟3秒钟调用:

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf invokeFlutterMethod:blockChannel];
});

而在Flutter端需要编写如下代码,在build方法中添加监听:

1
_platform.setMethodCallHandler(platformCallHandler);

实现platformCallHandlerflutterMethod方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "flutterMethod":
flutterMethod();
break;
}
}

void flutterMethod(){
setState(() {
_batteryLevel = '已经获取到电量了!!!';
});
}

然后再次运行程序,当获取完电量后3秒,按钮下方的文字会变成”已经获取到电量了!!!”(此处就不再提供Gif了)

附:本文的Demo

谢谢您的支持!