一篇看懂Android与Flutter之间的通讯
Flutter作为一种跨平台解决方案,经常会作为一个模组嵌入到原生Android与iOS应用中,Flutter与Android原生端的通讯必不可少。所以本文就来讲述一下Android如何与flutter进行通讯。
1、架构概述
讯息通过平台通道在native(host)与flutter(client)之间传递,如下图所示:
为了确保使用者界面能够正确响应,讯息都是以异步的方式进行传递。无论是native向flutter传送讯息,还是flutter向native传送讯息。
在flutter中,MethodChannel可以传送与方法呼叫相对应的讯息。在native平台上,MethodChannel在Android可以接收方法呼叫并返回结果。这些类可以帮助我们用很少的程式码就能开发平台外挂。
注意:本节内容来自flutter官网,读者可自行查阅。
2、平台通道资料型别支援和编解码器
平台通道可以使用提供的编解码器对讯息进行编解码,这些编解码器支援简单类似JSON的值的高效二进位制序列化,例如布林值,数字,字串,字节缓冲区以及这些的列表和对映。当你传送和接收值时,会自动对这些值进行序列化和反序列化。下表显示了如何在平台端接收Dart值,反之亦然:

关于编解码器,Android端提供了以下四种。
BinaryCodec:是最简单的一种编解码器,其返回值型别与入参的型别相同,均为二进位制格式(ByteBuffer)。由于BinaryCodec在编解码过程中什么都没做,只是原封不动的将二进位制资料返回。所以传递的资料在编解码时会免于拷贝,这种方式在传递的资料量比较大时很有用。比如从Android侧传入一张图片到Flutter侧显示。StandardMessageCodec:是BasicMessageChannel的预设编解码器,支援基础资料型别、列表及字典等。在编码时会先将资料写入到ByteArrayOutputStream流中,然后再将该流中的资料写入到ByteBuffer中。在解码时,直接从ByteBuffer中读取资料。StandardMethodCodec:是基于StandardMessageCodec的封装。是MethodChannel与EventChannel的预设编解码器。StringCodec:是用于字串与二进位制资料之间的编解码,其编码格式为UTF-8。在编码时会将String转成byte阵列,然后再将该阵列写入到ByteBuffer中。在解码时,直接从ByteBuffer中读取资料JSONMessageCodec:内部呼叫StringCodec来实现编解码。JSONMethodCodec:基于JSONMessageCodec的封装。可以在MethodChannel与EventChannel中使用。ByteBuffer是Nio中的一个类,顾名思义——就是一块储存字节的区域。它有两个实现类——DirectByteBuffer与HeapByteBuffer,DirectByteBuffer是直接在内存中开辟了一块区域来储存资料,而HeapByteBuffer是在JVM堆中开辟一块区域来储存资料,所以要想资料在DirectByteBuffer中与HeapByteBuffer互通,就需要进行一次拷贝。
3、通讯方式
前面讲了Android与flutter通讯的一些基础知识,下面就进入正题,来看Android如何与flutter进行通讯。Android与Flutter之间的通讯共有四种实现方式。
由于在初始化flutter页面时会传递一个字串——route,因此我们就可以拿route来做文章,传递自己想要传递的资料。该种方式仅支援单向资料传递且资料型别只能为字串,无返回值。通过EventChannel来实现,EventChannel仅支援资料单向传递,无返回值。通过MethodChannel来实现,MethodChannel支援资料双向传递,有返回值。通过BasicMessageChannel来实现,BasicMessageChannel支援资料双向传递,有返回值。下面就来看一下这几种方式的使用。
3.1、初始化时传值
主要是利用了建立flutter页面传递的route来做文章,笔者认为该种方式属于取巧,但还是可以用来传递资料。它的使用很简单,程式码如下。
首先来看Android程式码。
//第三个引数可以换成我们想要字串。
FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route");
在flutter中,我们只需要通过下面程式码来获取值即可。
void main() => runApp(MyApp(
initParams: window.defaultRouteName,
));
class MyApp extends StatelessWidget {
final String initParams;//既是前面传递的值——route
MyApp({Key key, @required this.initParams}) : super(key: key);
@override
Widget build(BuildContext context) {...}
}
通过该种方式就可以在初始化flutter时,Android给flutter传递资料。由于runApp仅会呼叫一次,所以该种方式只能传递一次资料且资料只能是字串。
使用window的相关API需要汇入包dart:ui
3.2、EventChannel
EventChannel是一种native向flutter传送资料的单向通讯方式,flutter无法返回任何资料给native。主要用于native向flutter传送手机电量变化、网络连线变化、陀螺仪、感测器等。它的使用方式如下。
首先来看Android程式码。
public class EventChannelPlugin implements EventChannel.StreamHandler {
private static final String TAG = EventChannelPlugin.class.getSimpleName();
private EventChannel.EventSink eventSink;
private Activity activity;
static EventChannelPlugin registerWith(FlutterView flutterView) {
EventChannelPlugin plugin = new EventChannelPlugin(flutterView);
new EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(plugin);
return plugin;
}
private EventChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
}
void send(Object params) {
if (eventSink != null) {
eventSink.success(params);
}
}
void sendError(String str1, String str2, Object params) {
if (eventSink != null) {
eventSink.error(str1, str2, params);
}
}
void cancel() {
if (eventSink != null) {
eventSink.endOfStream();
}
}
//第一个引数为flutter初始化EventChannel时返回的值,仅此一次
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
Log.i(TAG, "eventSink:" + eventSink);
Log.i(TAG, "Object:" + o.toString());
Toast.makeText(activity, "onListen——obj:" + o, Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel(Object o) {
Log.i(TAG, "onCancel:" + o.toString());
Toast.makeText(activity, "onCancel——obj:" + o, Toast.LENGTH_SHORT).show();
this.eventSink = null;
}
}
笔者对Android端程式码做了一个简单的封装,还是很好理解的。下面就来看flutter程式码实现。
class _MyHomePageState extends State {
EventChannel _eventChannelPlugin = EventChannel("EventChannelPlugin");
StreamSubscription _streamSubscription;
@override
void initState() {
_streamSubscription = _eventChannelPlugin
//["abc", 123, "你好"]对应着Android端onListen方法的第一个引数,可不传值
.receiveBroadcastStream(["abc", 123, "你好"])
.listen(_onToDart, onError: _onToDartError, onDone: _onDone);
super.initState();
}
@override
void dispose() {
if (_streamSubscription != null) {
_streamSubscription.cancel();
_streamSubscription = null;
}
super.dispose();
}
//native端传送正常资料
void _onToDart(message) {
print(message);
}
//当native出错时,传送的资料
void _onToDartError(error) {
print(error);
}
//当native传送资料完成时呼叫的方法,每一次传送完成就会呼叫
void _onDone() {
print("讯息传递完毕");
}
@override
Widget build(BuildContext context) {...}
}
上面就是通过EventChannel来进行通讯的程式码实现,呼叫EventChannelPlugin的send方法就能给flutter传送资料。
3.3、MethodChannel
MethodChannel是一种native与flutter之间互相传送资料的通讯方式,顾名思义,通过MethodChannel就能呼叫native与flutter中相对应的方法,该种方式有返回值。它的使用方式如下。
首先来看Android端的程式码实现。
public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {
private Activity activity;
private MethodChannel channel;
public static MethodChannelPlugin registerWith(FlutterView flutterView) {
MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin");
MethodChannelPlugin methodChannelPlugin = new MethodChannelPlugin((Activity) flutterView.getContext(), channel);
channel.setMethodCallHandler(methodChannelPlugin);
return methodChannelPlugin;
}
private MethodChannelPlugin(Activity activity, MethodChannel channel) {
this.activity = activity;
this.channel = channel;
}
//呼叫flutter端方法,无返回值
public void invokeMethod(String method, Object o) {
channel.invokeMethod(method, o);
}
//呼叫flutter端方法,有返回值
public void invokeMethod(String method, Object o, MethodChannel.Result result) {
channel.invokeMethod(method, o, result);
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {
case "send"://返回的方法名
//给flutter端的返回值
result.success("MethodChannelPlugin收到:" + methodCall.arguments);
Toast.makeText(activity, methodCall.arguments + "", Toast.LENGTH_SHORT).show();
if (activity instanceof FlutterAppActivity) {
((FlutterAppActivity) activity).showContent(methodCall.arguments);
}
break;
default:
result.notImplemented();
break;
}
}
}
笔者对Android端程式码做了一个简单的封装,还是很好理解的。下面就来看flutter程式码实现。
class _MyHomePageState extends State {
MethodChannel _methodChannel = MethodChannel("MethodChannelPlugin");
@override
void initState() {
_methodChannel.setMethodCallHandler((handler) => Future(() {
print("_methodChannel:${handler}");
//监听native传送的方法名及引数
switch (handler.method) {
case "send":
_send(handler.arguments);//handler.arguments表示native传递的方法引数
break;
}
}));
super.initState();
}
//native呼叫的flutter方法
void _send(arg) {
setState(() {
_content = arg;
});
}
String _resultContent = "";
//flutter呼叫native的相应方法
void _sendToNative() {
Future future =
_methodChannel.invokeMethod("send", _controller.text);
future.then((message) {
setState(() {
//message是native返回的资料
_resultContent = "返回值:" + message;
});
});
}
@override
Widget build(BuildContext context) {...}
}
上面就是通过MethodChannel来进行通讯的程式码实现。还是比较简单的。在Android端使用只需要呼叫MethodChannelPlugin的invokeMethod方法即可。在flutter端使用只需要参考_sendToNative方法的实现即可。
3.4、BasicMessageChannel
BasicMessageChannel是一种能够在native与flutter之间互相传送讯息的通讯方式,它支援资料型别最多,使用范围最广。EventChannel与MethodChannel的应用场景可以使用BasicMessageChannel来实现,但BasicMessageChannel的应用场景就不一定能够使用EventChannel与MethodChannel来实现。该方式有返回值。它的使用方式如下。
首先来看Android程式码的实现。
//这里支援的资料型别为String。
public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler {
private Activity activity;
private BasicMessageChannel messageChannel;
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
private BasicMessageChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
this.messageChannel = new BasicMessageChannel(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE);
messageChannel.setMessageHandler(this);
}
@Override
public void onMessage(String s, BasicMessageChannel.Reply reply) {
reply.reply("BasicMessageChannelPlugin收到:" + s);
if (activity instanceof FlutterAppActivity) {
((FlutterAppActivity) activity).showContent(s);
}
}
void send(String str, BasicMessageChannel.Reply reply) {
messageChannel.send(str, reply);
}
}
笔者对Android端程式码做了一个简单的封装,还是很好理解的。下面就来看flutter程式码实现。
class _MyHomePageState extends State {
//StringCodec()为编码格式
BasicMessageChannel _basicMessageChannel =
BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());
@override
void initState() {
_basicMessageChannel.setMessageHandler((message) => Future(() {
print(message);
//message为native传递的资料
setState(() {
_content = message;
});
//给Android端的返回值
return "收到Native讯息:" + message;
}));
_controller = TextEditingController();
super.initState();
}
//向native传送讯息
void _sendToNative() {
Future future = _basicMessageChannel.send(_controller.text);
future.then((message) {
_resultContent = "返回值:" + message;
});
}
@override
Widget build(BuildContext context) {...}
}
上面就是通过BasicMessageChannel来进行通讯的程式码实现。在Android端只需要呼叫BasicMessageChannelPlugin的send方法就可以向flutter传送资料,BasicMessageChannel.Reply是返回值的回拨方法。在flutter端使用只需要参考_sendToNative方法的实现即可。
4、通讯原理
从分析Android与Flutter通讯的源代码来看,实现还是比较简单的,都是以ByteBuffer为资料载体,然后通过BinaryMessenger来发送与接收资料。整体设计如下。
从图中可以看出,Android侧与flutter侧采用了相同的设计。前面说过通讯时是异步进行的,那么执行绪切换在哪?其实是在系统底层实现的。在Android与Flutter通讯中,系统底层遮蔽了执行绪切换、资料拷贝等大量复杂操作。使得Android侧与flutter侧能方便的来进行通讯。
在Android侧,BinaryMessenger是一个界面,在FlutterView中实现了该界面,在BinaryMessenger的方法中通过JNI来与系统底层沟通。在Flutter侧,BinaryMessenger是一个类,该类的作用就是与类window沟通,而类window才真正与系统底层沟通。
关于通讯的底层实现可以去阅读闲鱼的技术文章——深入理解Flutter Platform Channel,这篇文章很好的讲述了Flutter与Native通讯的系统底层原理。
5、总结
在Android与Flutter混合开发模式下,相互之间通讯的场景肯定不会少。了解Android与Flutter之间通讯的各种方式及使用,有助于选用合理的方式来实现。最后
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高阶UI、效能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程式、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
需要展开的架构学习笔记导图的可以:后台私信回复“安卓资料”免费获取~
