Flutter 混合开发(Android)Flutter跟Native相互通信
前言
Flutter 作为混合开发,跟native端做一些交互在所难免,比如说调用原生系统传感器、原生端的网络框架进行数据请求就会用到 Flutter 调用android 及android 原生调用 Flutter的方法,这里就涉及到Platform Channels(平台通道)
Platform Channels (平台通道)
Flutter 通过Channel 与客户端之间传递消息,如图:
图中就是通过MethodChannel的方式实现Flutter 与客户端之间的消息传递。MethodChannel是Platform Channels中的一种,Flutter有三种通信类型:
BasicMessageChannel:用于传递字符串和半结构化的信息
MethodChannel:用于传递方法调用(method invocation)通常用来调用native中某个方法
EventChannel: 用于数据流(event streams)的通信。有监听功能,比如电量变化之后直接推送数据给flutter端。
复制为了保证UI的响应,通过Platform Channels传递的消息都是异步的。
更多关于channel原理可以去看这篇文章:channel原理篇
Platform Channels 使用
1.MethodChannel的使用
原生客户端写法(以Android 为例)
首先定义一个获取手机电量方法
private int getBatteryLevel() {
return 90;
}
复制这函数是要给Flutter 调用的方法,此时就需要通过 MethodChannel
来建立这个通道了。
首先新增一个初始化 MethodChannel
的方法
private String METHOD_CHANNEL = "common.flutter/battery";
private String GET_BATTERY_LEVEL = "getBatteryLevel";
private MethodChannel methodChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
initMethodChannel();
getFlutterView().postDelayed(() ->
methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.d(TAG, "get_message:" + o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
}
@Override
public void notImplemented() {
}
}), 5000);
}
private void initMethodChannel() {
methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
methodChannel.setMethodCallHandler(
(methodCall, result) -> {
if (methodCall.method.equals(GET_BATTERY_LEVEL)) {
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
});
}
private int getBatteryLevel() {
return 90;
}
复制METHOD_CHANNEL
用于和flutter交互的标识,由于一般情况下会有多个channel,在app里面需要保持唯一性
MethodChannel
都是保存在以通道名为Key的Map中。所以要是设了两个名字一样的channel,只有后设置的那个会生效。
onMethodCall
有两个参数,onMethodCall
里包含要调用的方法名称和参数。Result是给Flutter的返回值。方法名是客户端与Flutter统一设定。通过if/switch语句判断 MethodCall.method
来区分不同的方法,在我们的例子里面我们只会处理名为“getBatteryLevel”的调用。在调用本地方法获取到电量以后通过 result.success(batteryLevel)
调用把电量值返回给Flutter。
MethodChannel-Flutter 端
直接先看一下Flutter端的代码
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
static const platform = const MethodChannel('common.flutter/battery');
void _incrementCounter() {
setState(() {
_counter++;
_getBatteryLevel();
});
}
@override
Widget build(BuildContext context) {
platform.setMethodCallHandler(platformCallHandler);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
Text('$_batteryLevel'),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
String _batteryLevel = 'Unknown battery level.';
Future<Null> _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;
});
}
//客户端调用
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "get_message":
return "Hello from Flutter";
break;
}
}
}
复制上面代码解析:
首先,定义一个常量result.success(platform)
,和Android客户端定义的channel一致;
接下来定义一个 result.success(_getBatteryLevel())
方法,用来调用Android 端的方法,result.success(final int result = await platform.invokeMethod('getBatteryLevel');)
这行代码就是通过通道来调用Native(Android)方法了。因为MethodChannel是异步调用的,所以这里必须要使用await关键字。
在上面Android代码中我们把获取到的电量通过result.success(batteryLevel);
返回给Flutter。这里await表达式执行完成以后电量就直接赋值给result变量了。然后通过result.success(setState);
去改变Text显示值。到这里为止,是通过Flutter端调用原生客户端方法。
MethodChannel
其实是一个可以双向调用的方法,在上面的代码中,其实我们也体现了,通过原生客户端调用Flutter的方法。
在原生端通过 methodChannel.invokeMethod
的方法调用
methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.d(TAG, "get_message:" + o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
}
@Override
public void notImplemented() {
}
});
复制在Flutter端就需要给MethodChannel
设置一个MethodCallHandler
static const platform = const MethodChannel('common.flutter/battery');
platform.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "get_message":
return "Hello from Flutter";
break;
}
}
复制以上就是MethodChannel
的相关用法了。
EventChannel
将数据推送给Flutter端,类似我们常用的推送功能,有需要就推送给Flutter端,是否需要去处理这个推送由Flutter那边决定。相对于MethodChannel
是主动获取,EventChannel
则是被动推送。
EventChannel 原生客户端写法
private String EVENT_CHANNEL = "common.flutter/message";
private int count = 0;
private Timer timer;
private void initEventChannel() {
new EventChannel(getFlutterView(), EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
timer.schedule(new TimerTask() {
@Override
public void run() {
if (count < 10) {
count++;
events.success("当前时间:" + System.currentTimeMillis());
} else {
timer.cancel();
}
}
}, 1000, 1000);
}
@Override
public void onCancel(Object o) {
}
});
}
复制在上面的代码中,我们做了一个定时器,每秒向Flutter推送一个消息,告诉Flutter我们当前时间。为了防止一直倒计时,我这边做了个计数,超过10次就停止发送。
EventChannel Flutter端
String message = "not message";
static const eventChannel = const EventChannel('common.flutter/message');
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
void _onEvent(Object event) {
setState(() {
message =
"message: $event";
});
}
void _onError(Object error) {
setState(() {
message = 'message: unknown.';
});
}
复制上面的代码就是Flutter端接收原生客户端数据,通过_onEvent
来接收数据,将数据显示Text
。这个实现相对简单,如果要达到业务分类,需要将数据封装成json,通过json数据包装一些对应业务标识和数据来做区分。
BasicMessageChannel
BasicMessageChannel (主要是传递字符串和一些半结构体的数据)
BasicMessageChannel Android端
private void initBasicMessageChannel() {
BasicMessageChannel<Object> basicMessageChannel = new BasicMessageChannel<>(getFlutterView(), BASIC_CHANNEL, StandardMessageCodec.INSTANCE);
//主动发送消息到flutter 并接收flutter消息回复
basicMessageChannel.send("send basic message", (object)-> {
Log.e(TAG, "receive reply msg from flutter:" + object.toString());
});
//接收flutter消息 并发送回复
basicMessageChannel.setMessageHandler((object, reply)-> {
Log.e(TAG, "receive msg from flutter:" + object.toString());
reply.reply("reply:got your message");
});
}
复制BasicMessageChannel Flutter端
static const basicChannel = const BasicMessageChannel('common.flutter/basic', StandardMessageCodec());
//发送消息到原生客户端 并且接收到原生客户端的回复
Future<String> sendMessage() async {
String reply = await basicChannel.send('this is flutter');
print("receive reply msg from native:$reply");
return reply;
}
//接收原生消息 并发送回复
void receiveMessage() async {
basicChannel.setMessageHandler((msg) async {
print("receive from Android:$msg");
return "get native message";
});
复制上面例子中用到的编解码器为StandardMessageCodec ,例子中通信都是String,用StringCodec也可以。
以上就是Flutter提供三种platform和dart端的消息通信方式。