|
|
51CTO旗下网站
|
|
移动端

一篇看懂Android与Flutter之间的通信

Flutter作为一种跨平台解决方案,经常会作为一个模块嵌入到原生Android与iOS应用中,Flutter与Android原生端的通信必不可少。所以本文就来讲述一下Android如何与flutter进行通信。

作者:Android高级架构师来源:今日头条|2019-06-25 10:53

【大咖·来了 第7期】10月24日晚8点观看《智能导购对话机器人实践》

Flutter作为一种跨平台解决方案,经常会作为一个模块嵌入到原生Android与iOS应用中,Flutter与Android原生端的通信必不可少。所以本文就来讲述一下Android如何与flutter进行通信。

1、架构概述

消息通过平台通道在native(host)与flutter(client)之间传递,如下图所示:

一篇看懂Android与Flutter之间的通信

为了确保用户界面能够正确响应,消息都是以异步的方式进行传递。无论是native向flutter发送消息,还是flutter向native发送消息。

在flutter中,MethodChannel可以发送与方法调用相对应的消息。在native平台上,MethodChannel在Android可以接收方法调用并返回结果。这些类可以帮助我们用很少的代码就能开发平台插件。

注意:本节内容来自flutter官网,读者可自行查阅。

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

平台通道可以使用提供的编解码器对消息进行编解码,这些编解码器支持简单类似JSON的值的高效二进制序列化,例如布尔值,数字,字符串,字节缓冲区以及这些的列表和映射。当你发送和接收值时,会自动对这些值进行序列化和反序列化。

下表显示了如何在平台端接收Dart值,反之亦然:

一篇看懂Android与Flutter之间的通信

关于编解码器,Android端提供了以下几种。

  1. BinaryCodec:是最简单的一种编解码器,其返回值类型与入参的类型相同,均为二进制格式(ByteBuffer)。由于BinaryCodec在编解码过程中什么都没做,只是原封不动的将二进制数据返回。所以传递的数据在编解码时会免于拷贝,这种方式在传递的数据量比较大时很有用。比如从Android侧传入一张图片到Flutter侧显示。
  2. StandardMessageCodec:是BasicMessageChannel的默认编解码器,支持基础数据类型、列表及字典等。在编码时会先将数据写入到ByteArrayOutputStream流中,然后再将该流中的数据写入到ByteBuffer中。在解码时,直接从ByteBuffer中读取数据。
  3. StandardMethodCodec:是基于StandardMessageCodec的封装。是MethodChannel与EventChannel的默认编解码器。
  4. StringCodec:是用于字符串与二进制数据之间的编解码,其编码格式为UTF-8。在编码时会将String转成byte数组,然后再将该数组写入到ByteBuffer中。在解码时,直接从ByteBuffer中读取数据
  5. JSONMessageCodec:内部调用StringCodec来实现编解码。
  6. JSONMethodCodec:基于JSONMessageCodec的封装。可以在MethodChannel与EventChannel中使用。

ByteBuffer是Nio中的一个类,顾名思义——就是一块存储字节的区域。它有两个实现类——DirectByteBuffer与HeapByteBuffer,DirectByteBuffer是直接在内存中开辟了一块区域来存储数据,而HeapByteBuffer是在JVM堆中开辟一块区域来存储数据,所以要想数据在DirectByteBuffer中与HeapByteBuffer互通,就需要进行一次拷贝。

3、通信方式

前面讲了Android与flutter通信的一些基础知识,下面就进入正题,来看Android如何与flutter进行通信。

Android与Flutter之间的通信共有四种实现方式。

  1. 由于在初始化flutter页面时会传递一个字符串——route,因此我们就可以拿route来做文章,传递自己想要传递的数据。该种方式仅支持单向数据传递且数据类型只能为字符串,无返回值。
  2. 通过EventChannel来实现,EventChannel仅支持数据单向传递,无返回值。
  3. 通过MethodChannel来实现,MethodChannel支持数据双向传递,有返回值。
  4. 通过BasicMessageChannel来实现,BasicMessageChannel支持数据双向传递,有返回值。

下面就来看一下这几种方式的使用。

3.1、初始化时传值

主要是利用了创建flutter页面传递的route来做文章,笔者认为该种方式属于取巧,但还是可以用来传递数据。它的使用很简单,代码如下。

首先来看Android代码。

  1. //第三个参数可以换成我们想要字符串。 
  2. FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route"); 

在flutter中,我们只需要通过下面代码来获取值即可。

  1. void main() => runApp(MyApp( 
  2.  initParams: window.defaultRouteName, 
  3.  )); 
  4. class MyApp extends StatelessWidget { 
  5.  final String initParams;//既是前面传递的值——route 
  6.  MyApp({Key key, @required this.initParams}) : super(keykey); 
  7.  @override 
  8.  Widget build(BuildContext context) {...} 

通过该种方式就可以在初始化flutter时,Android给flutter传递数据。由于runApp仅会调用一次,所以该种方式只能传递一次数据且数据只能是字符串。

  • 使用window的相关API需要导入包dart:ui

3.2、EventChannel

EventChannel是一种native向flutter发送数据的单向通信方式,flutter无法返回任何数据给native。主要用于native向flutter发送手机电量变化、网络连接变化、陀螺仪、传感器等。它的使用方式如下。

首先来看Android代码。

  1. public class EventChannelPlugin implements EventChannel.StreamHandler { 
  2.  private static final String TAG = EventChannelPlugin.class.getSimpleName(); 
  3.  private EventChannel.EventSink eventSink; 
  4.  private Activity activity; 
  5.  static EventChannelPlugin registerWith(FlutterView flutterView) { 
  6.  EventChannelPlugin plugin = new EventChannelPlugin(flutterView); 
  7.  new EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(plugin); 
  8.  return plugin; 
  9.  } 
  10.  private EventChannelPlugin(FlutterView flutterView) { 
  11.  this.activity = (Activity) flutterView.getContext(); 
  12.  } 
  13.  void send(Object params) { 
  14.  if (eventSink != null) { 
  15.  eventSink.success(params); 
  16.  } 
  17.  } 
  18.  void sendError(String str1, String str2, Object params) { 
  19.  if (eventSink != null) { 
  20.  eventSink.error(str1, str2, params); 
  21.  } 
  22.  } 
  23.  void cancel() { 
  24.  if (eventSink != null) { 
  25.  eventSink.endOfStream(); 
  26.  } 
  27.  } 
  28.  //第一个参数为flutter初始化EventChannel时返回的值,仅此一次 
  29.  @Override 
  30.  public void onListen(Object o, EventChannel.EventSink eventSink) { 
  31.  this.eventSink = eventSink; 
  32.  Log.i(TAG, "eventSink:" + eventSink); 
  33.  Log.i(TAG, "Object:" + o.toString()); 
  34.  Toast.makeText(activity, "onListen——obj:" + o, Toast.LENGTH_SHORT).show(); 
  35.  } 
  36.  @Override 
  37.  public void onCancel(Object o) { 
  38.  Log.i(TAG, "onCancel:" + o.toString()); 
  39.  Toast.makeText(activity, "onCancel——obj:" + o, Toast.LENGTH_SHORT).show(); 
  40.  this.eventSink = null
  41.  } 

笔者对Android端代码做了一个简单的封装,还是很好理解的。下面就来看flutter代码实现。

  1. class _MyHomePageState extends State<MyHomePage> { 
  2.  EventChannel _eventChannelPlugin = EventChannel("EventChannelPlugin"); 
  3.  StreamSubscription _streamSubscription; 
  4.  @override 
  5.  void initState() { 
  6.  _streamSubscription = _eventChannelPlugin 
  7.  //["abc", 123, "你好"]对应着Android端onListen方法的第一个参数,可不传值 
  8.  .receiveBroadcastStream(["abc", 123, "你好"]) 
  9.  .listen(_onToDart, onError: _onToDartError, onDone: _onDone); 
  10.  super.initState(); 
  11.  } 
  12.  @override 
  13.  void dispose() { 
  14.  if (_streamSubscription != null) { 
  15.  _streamSubscription.cancel(); 
  16.  _streamSubscription = null
  17.  } 
  18.  super.dispose(); 
  19.  } 
  20.  //native端发送正常数据 
  21.  void _onToDart(message) { 
  22.  print(message); 
  23.  } 
  24.  //当native出错时,发送的数据 
  25.  void _onToDartError(error) { 
  26.  print(error); 
  27.  } 
  28.  //当native发送数据完成时调用的方法,每一次发送完成就会调用 
  29.  void _onDone() { 
  30.  print("消息传递完毕"); 
  31.  } 
  32.  @override 
  33.  Widget build(BuildContext context) {...} 

上面就是通过EventChannel来进行通信的代码实现,调用EventChannelPlugin的send方法就能给flutter发送数据。

3.3、MethodChannel

MethodChannel是一种native与flutter之间互相发送数据的通信方式,顾名思义,通过MethodChannel就能调用native与flutter中相对应的方法,该种方式有返回值。它的使用方式如下。

首先来看Android端的代码实现。

  1. public class MethodChannelPlugin implements MethodChannel.MethodCallHandler { 
  2.  private Activity activity; 
  3.  private MethodChannel channel; 
  4.  public static MethodChannelPlugin registerWith(FlutterView flutterView) { 
  5.  MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin"); 
  6.  MethodChannelPlugin methodChannelPlugin = new MethodChannelPlugin((Activity) flutterView.getContext(), channel); 
  7.  channel.setMethodCallHandler(methodChannelPlugin); 
  8.  return methodChannelPlugin; 
  9.  } 
  10.  private MethodChannelPlugin(Activity activity, MethodChannel channel) { 
  11.  this.activity = activity; 
  12.  this.channel = channel; 
  13.  } 
  14.  //调用flutter端方法,无返回值 
  15.  public void invokeMethod(String method, Object o) { 
  16.  channel.invokeMethod(method, o); 
  17.  } 
  18.  //调用flutter端方法,有返回值 
  19.  public void invokeMethod(String method, Object o, MethodChannel.Result result) { 
  20.  channel.invokeMethod(method, o, result); 
  21.  } 
  22.  @Override 
  23.  public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { 
  24.  switch (methodCall.method) { 
  25.  case "send"://返回的方法名 
  26.  //给flutter端的返回值 
  27.  result.success("MethodChannelPlugin收到:" + methodCall.arguments); 
  28.  Toast.makeText(activity, methodCall.arguments + "", Toast.LENGTH_SHORT).show(); 
  29.  if (activity instanceof FlutterAppActivity) { 
  30.  ((FlutterAppActivity) activity).showContent(methodCall.arguments); 
  31.  } 
  32.  break; 
  33.  default
  34.  result.notImplemented(); 
  35.  break; 
  36.  } 
  37.  } 

笔者对Android端代码做了一个简单的封装,还是很好理解的。下面就来看flutter代码实现。

  1. class _MyHomePageState extends State<MyHomePage> { 
  2.  MethodChannel _methodChannel = MethodChannel("MethodChannelPlugin"); 
  3.  @override 
  4.  void initState() { 
  5.  _methodChannel.setMethodCallHandler((handler) => Future<String>(() { 
  6.  print("_methodChannel:${handler}"); 
  7.  //监听native发送的方法名及参数 
  8.  switch (handler.method) { 
  9.  case "send"
  10.  _send(handler.arguments);//handler.arguments表示native传递的方法参数 
  11.  break; 
  12.  } 
  13.  })); 
  14.  super.initState(); 
  15.  } 
  16.  //native调用的flutter方法 
  17.  void _send(arg) { 
  18.  setState(() { 
  19.  _content = arg; 
  20.  }); 
  21.  } 
  22.  String _resultContent = ""
  23.  //flutter调用native的相应方法 
  24.  void _sendToNative() { 
  25.  Future<String> future = 
  26.  _methodChannel.invokeMethod("send", _controller.text); 
  27.  future.then((message) { 
  28.  setState(() { 
  29.  //message是native返回的数据 
  30.  _resultContent = "返回值:" + message; 
  31.  }); 
  32.  }); 
  33.  } 
  34.  @override 
  35.  Widget build(BuildContext context) {...} 

上面就是通过MethodChannel来进行通信的代码实现。还是比较简单的。在Android端使用只需要调用MethodChannelPlugin的invokeMethod方法即可。在flutter端使用只需要参考_sendToNative方法的实现即可。

3.4、BasicMessageChannel

BasicMessageChannel是一种能够在native与flutter之间互相发送消息的通信方式,它支持数据类型最多,使用范围最广。EventChannel与MethodChannel的应用场景可以使用BasicMessageChannel来实现,但BasicMessageChannel的应用场景就不一定能够使用EventChannel与MethodChannel来实现。该方式有返回值。它的使用方式如下。

首先来看Android代码的实现。

  1. //这里支持的数据类型为String。 
  2. public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler<String> { 
  3.  private Activity activity; 
  4.  private BasicMessageChannel<String> messageChannel; 
  5.  static BasicMessageChannelPlugin registerWith(FlutterView flutterView) { 
  6.  return new BasicMessageChannelPlugin(flutterView); 
  7.  } 
  8.  private BasicMessageChannelPlugin(FlutterView flutterView) { 
  9.  this.activity = (Activity) flutterView.getContext(); 
  10.  this.messageChannel = new BasicMessageChannel<String>(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE); 
  11.  messageChannel.setMessageHandler(this); 
  12.  } 
  13.  @Override 
  14.  public void onMessage(String s, BasicMessageChannel.Reply<String> reply) { 
  15.  reply.reply("BasicMessageChannelPlugin收到:" + s); 
  16.  if (activity instanceof FlutterAppActivity) { 
  17.  ((FlutterAppActivity) activity).showContent(s); 
  18.  } 
  19.  } 
  20.  void send(String str, BasicMessageChannel.Reply<String> reply) { 
  21.  messageChannel.send(str, reply); 
  22.  } 

笔者对Android端代码做了一个简单的封装,还是很好理解的。下面就来看flutter代码实现。

  1. class _MyHomePageState extends State<MyHomePage> { 
  2.  //StringCodec()为编码格式 
  3.  BasicMessageChannel<String> _basicMessageChannel = 
  4.  BasicMessageChannel("BasicMessageChannelPlugin", StringCodec()); 
  5.  @override 
  6.  void initState() { 
  7.  _basicMessageChannel.setMessageHandler((message) => Future<String>(() { 
  8.  print(message); 
  9.  //message为native传递的数据 
  10.  setState(() { 
  11.  _content = message; 
  12.  }); 
  13.  //给Android端的返回值 
  14.  return "收到Native消息:" + message; 
  15.  })); 
  16.  _controller = TextEditingController(); 
  17.  super.initState(); 
  18.  } 
  19.  //向native发送消息 
  20.  void _sendToNative() { 
  21.  Future<String> future = _basicMessageChannel.send(_controller.text); 
  22.  future.then((message) { 
  23.  _resultContent = "返回值:" + message; 
  24.  }); 
  25.  } 
  26.  @override 
  27.  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侧与flutter侧能方便的来进行通信。

在Android侧,BinaryMessenger是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通。在Flutter侧,BinaryMessenger是一个类,该类的作用就是与类window沟通,而类window才真正与系统底层沟通。

5、总结

在Android与Flutter混合开发模式下,相互之间通信的场景肯定不会少。了解Android与Flutter之间通信的各种方式及使用,有助于选用合理的方式来实现。

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

【编辑推荐】

  1. 华为Android Q首批升级机型曝光:共11款
  2. Flutter 开发中的一些 Tips
  3. 你必须要掌握的Android冷启动优化
  4. 为期两年的研究揭示了Android的大问题
  5. 两个星期,用Flutter撸个APP
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
24H热文
一周话题
本月获赞

订阅专栏+更多

16招轻松掌握PPT技巧

16招轻松掌握PPT技巧

GET职场加薪技能
共16章 | 晒书包

292人订阅学习

20个局域网建设改造案例

20个局域网建设改造案例

网络搭建技巧
共20章 | 捷哥CCIE

645人订阅学习

WOT2019全球人工智能技术峰会

WOT2019全球人工智能技术峰会

通用技术、应用领域、企业赋能三大章节,13大技术专场,60+国内外一线人工智能精英大咖站台,分享人工智能的平台工具、算法模型、语音视觉等技术主题,助力人工智能落地。
共50章 | WOT峰会

0人订阅学习

读 书 +更多

计算机网络技术

本书是为北大燕工教育研究院编写的计算机网络技术的学习教材。它以实际教学大纲为依据,全面系统的介绍了计算机网络技术知识,对于一个...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO播客