目录

前言

BasicMessageChannel

Android端-

(1)不使用engine cache预热

(2)使用engine cache预热

Flutter端-

MethodChannel

Android端=

Flutter端=

源码分析=

EventChannel

Android端+

Flutter端+

源码分析+

总结


前言

上一篇文章Flutter混合开发:Android中如何启动Flutter中我们介绍了如何在Native(Android项目)中启动Flutter,展示Flutter页面。但是在开发过程中,很多时候并不是简单的展示一个页面即可,还会涉及到各种交互,比如传递一些消息。

本篇文章就简单介绍一下Flutter与原生Native的三种交互方式:

BasicMessageChannel、MethodChannel和EventChannel。

BasicMessageChannel

虽然说是三种交互方式,但是其实本质都是一种,这个我们后面会解释。

先来看看BasicMessageChannel。它可以实现双方交互,发送一些简单消息,消息类型Object,但是并不是所有Object都可以,基础类型及基础类型的数组、list、map是可以的。这个可以参考BasicMessageChannel的源码:

  public void send(@Nullable T message, @Nullable final Reply<T> callback) {messenger.send(name,codec.encodeMessage(message),callback == null ? null : new IncomingReplyHandler(callback));}

可以看到进行了encode,这个codec一般是StandardMessageCodec,它的encodeMessage函数源码:

  public ByteBuffer encodeMessage(Object message) {if (message == null) {return null;}final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();writeValue(stream, message);final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());buffer.put(stream.buffer(), 0, stream.size());return buffer;}

这里writeValue的源码:

protected void writeValue(ByteArrayOutputStream stream, Object value) {if (value == null || value.equals(null)) {stream.write(NULL);} else if (value == Boolean.TRUE) {stream.write(TRUE);} else if (value == Boolean.FALSE) {stream.write(FALSE);} else if (value instanceof Number) {if (value instanceof Integer || value instanceof Short || value instanceof Byte) {stream.write(INT);writeInt(stream, ((Number) value).intValue());} else if (value instanceof Long) {stream.write(LONG);writeLong(stream, (long) value);} else if (value instanceof Float || value instanceof Double) {stream.write(DOUBLE);writeAlignment(stream, 8);writeDouble(stream, ((Number) value).doubleValue());} else if (value instanceof BigInteger) {stream.write(BIGINT);writeBytes(stream, ((BigInteger) value).toString(16).getBytes(UTF8));} else {throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());}} else if (value instanceof String) {stream.write(STRING);writeBytes(stream, ((String) value).getBytes(UTF8));} else if (value instanceof byte[]) {stream.write(BYTE_ARRAY);writeBytes(stream, (byte[]) value);} else if (value instanceof int[]) {stream.write(INT_ARRAY);final int[] array = (int[]) value;writeSize(stream, array.length);writeAlignment(stream, 4);for (final int n : array) {writeInt(stream, n);}} else if (value instanceof long[]) {stream.write(LONG_ARRAY);final long[] array = (long[]) value;writeSize(stream, array.length);writeAlignment(stream, 8);for (final long n : array) {writeLong(stream, n);}} else if (value instanceof double[]) {stream.write(DOUBLE_ARRAY);final double[] array = (double[]) value;writeSize(stream, array.length);writeAlignment(stream, 8);for (final double d : array) {writeDouble(stream, d);}} else if (value instanceof List) {stream.write(LIST);final List<?> list = (List) value;writeSize(stream, list.size());for (final Object o : list) {writeValue(stream, o);}} else if (value instanceof Map) {stream.write(MAP);final Map<?, ?> map = (Map) value;writeSize(stream, map.size());for (final Entry<?, ?> entry : map.entrySet()) {writeValue(stream, entry.getKey());writeValue(stream, entry.getValue());}} else {throw new IllegalArgumentException("Unsupported value: " + value);}}

下面看一下如何来使用它,以Android端为例。

Android端-

(1)不使用engine cache预热

如果不使用engine cache,那么在FlutterActivity的继承类中重写configureFlutterEngine:

class MainActivity : FlutterActivity() {var channel : BasicMessageChannel? = nulloverride fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)var channel = BasicMessageChannel<String>(flutterEngine.dartExecutor.binaryMessenger,"test" ,StringCodec.INSTANCE)channel.setMessageHandler { message, reply ->Log.e("recieve", message)}}
}

注意这里第二个参数"test"是这通道(channel)的名称,两边名称一致才能进行通信。

第三个参数是消息的编解码器,这里我们因为是简单的示例,消息是字符串String,所以用StringCodec。

StringCodec是MessageCodec接口的实现,除了它还有BinaryCodec,JsonMessageCodec,StandardMessageCodec。另外我们还可以自己实现MessageCodec,实现它的两个函数即可,它的源码如下:

public interface MessageCodec<T> {/*** Encodes the specified message into binary.** @param message the T message, possibly null.* @return a ByteBuffer containing the encoding between position 0 and the current position, or*     null, if message is null.*/@NullableByteBuffer encodeMessage(@Nullable T message);/*** Decodes the specified message from binary.** @param message the {@link ByteBuffer} message, possibly null.* @return a T value representation of the bytes between the given buffer's current position and*     its limit, or null, if message is null.*/@NullableT decodeMessage(@Nullable ByteBuffer message);
}

最后,MessageHandler用于接受从Flutter传递过来的消息。这里简单的将消息打印出来。

当需要向flutter发送消息时,执行:

channel?.send("android call")

即可

(2)使用engine cache预热

一般情况我们在Application中添加cache,如下:

class App : Application() {companion object{...lateinit var flutterEngine2 : FlutterEngine}override fun onCreate() {super.onCreate()...flutterEngine2 = FlutterEngine(this)flutterEngine2.navigationChannel.setInitialRoute("second")flutterEngine2.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())FlutterEngineCache.getInstance().put("second", flutterEngine2)}
}

这里我们为second这个flutter页面创建engine并加入cache进行预热。

如果我们想使用这个engine发送消息,那么可以直接创建BasicMessageChannel

var channel = BasicMessageChannel<String>(App.flutterEngine2.dartExecutor.binaryMessenger,"test" ,StandardMessageCodec.INSTANCE as MessageCodec<String>)
channel.setMessageHandler { message, reply ->Log.e("recieve", message)
}

后续与上面就一样了。

Flutter端-

步骤基本一样,先创建

static const messageChannel = const BasicMessageChannel("test", StringCodec());

这里通道名称保持与native一致。

设置回调:

    messageChannel.setMessageHandler((message) async{print(message)});

发送消息:

messageChannel.send("flutter call");

这样就实现了Native和Flutter的双向消息交互。

MethodChannel

用于双方函数的调用,使用方法与BasicMessageChannel相似,其实本质上是一样的。我们先来看看如何使用它。

Android端=

与BasicMessageChannel一样预热和不预热可以有两种不同的处理,但是其实最终都是获取到FlutterEngine对象,所以就不赘述了,直接使用即可。代码如下:

  //创建var channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger,"test")//回调,根据call执行native函数channel.setMethodCallHandler { call, result ->when(call.method){"flutterCall" -> {//执行我们自定义的对应函数flutterCall(call.arguments)}else -> {}}}

这里flutterCall是响应Flutter发送过来的请求,我们定义一个对应的函数来处理,如:

    fun flutterCall(arguments : Object){Log.e("flutterCall", "message:" + arguments.toString())}

然后我们可以通过invokeMethod函数来执行Flutter函数,如:

  //执行flutter函数channel.invokeMethod("androidCall", "android message")

Flutter端=

流程一样,代码如下:

//创建
static const methodChannel = const MethodChannel("test");
//回调,根据call执行flutter函数methodChannel.setMethodCallHandler((call) async {switch(call.method){case "androidCall"://执行自定义的对应函数androidCall(call.arguments);break;}});
//执行native函数
methodChannel.invokeMethod("flutterCall", "flutter message");

源码分析=

在分析BasicMessageChannel时我们知道它的send函数其实是调用了messenger.send(...),这个messenger是BinaryMessenger,就是构造函数的第一个参数。MethodCannel也是一样,它的invokeMethod函数源码如下:

  @UiThreadpublic void invokeMethod(String method, @Nullable Object arguments, @Nullable Result callback) {messenger.send(name,codec.encodeMethodCall(new MethodCall(method, arguments)),callback == null ? null : new IncomingResultHandler(callback));}

可以看到,最终还是调用了BinaryMessenger的send函数。只不过将invokeMethod的两个参数(String类型的函数名method和Object类型的参数arguments)封装到MethodCall中。

再来看回调的处理,上面invokeMethod函数中可以看到,用IncomingResultHandler将callback进行了封装,它的关键源码如下:

  private final class IncomingMethodCallHandler implements BinaryMessageHandler {private final MethodCallHandler handler;IncomingMethodCallHandler(MethodCallHandler handler) {this.handler = handler;}@Override@UiThreadpublic void onMessage(ByteBuffer message, final BinaryReply reply) {final MethodCall call = codec.decodeMethodCall(message);try {handler.onMethodCall(call,new Result() {...});} catch (RuntimeException e) {...}}...}

可以看到在收到消息onMessage后先将消息解析成MethodCall在执行callback,这样就可以直接获取到函数名及参数了。

通过上面我们知道MethodChannel和BasicMessageChannel本质是一样的,只不过经过了一层MethodCall的封装,方便直接获取函数名和参数。

EventChannel

EventChannel与上面两个都不太一样,它是flutter发起,native处理并返回结果,flutter再处理结果。说它是单方向通道也不是很准确,但是native无法主动发起,所以更像是一个c/s结构。

先来看看如何使用。

Android端+

同样需要FlutterEngine对象,代码如下:

//创建
var channel = EventChannel(flutterEngine.dartExecutor.binaryMessenger,"test")
//设置处理handler
channel.setStreamHandler(object : StreamHandler(), EventChannel.StreamHandler {override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {//根据arguments处理arguments?.let {...//将处理结果返回,可能成功也可能失败events?.success("android back")//events?.error("errorcode", "errormssage", null)//如果不返回,即success和error都不执行,则需要执行endOfStream//events?.endOfStream()}}override fun onCancel(arguments: Any?) {//执行取消操作}
})

上面提到Native无法主动发起,所以就没有类似上面send或invokeMethod函数。

Flutter端+

通过receiveBroadcastStream来发送event请求,并通过linsten来监听返回。

//创建
static const eventChannel = const EventChannel("test");
//发送arguments给native处理,并监听结果
eventChannel.receiveBroadcastStream(["flutter event"]).listen((event) {//返回成功结果,处理print(event.toString());
}, onError: (event){//返回错误结果,处理
}, onDone: (){//执行完成处理
});

源码分析+

总结

Flutter如何与Native(Android)进行交互相关推荐

  1. Flutter混合开发:Android中如何启动Flutter

    目录 现有项目中引入Flutter 启动flutter页面 加速启动 启动传参 flutter可以独立完成项目,但是在现有项目情况下最好的方式就是混合开发,逐步过渡.这样就会共存native和flut ...

  2. 全网最全 Flutter 与 React Native 深入对比分析

    作为 GSY 开源系列的作者,在去年也整理过 <移动端跨平台开发的深度解析> 的对比文章,时隔一年之后,本篇将重新由 环境搭建.实现原理.编程开发.插件开发.编译运行.性能稳定.发展未来 ...

  3. android flutter 混合开发,Flutter(六)Android与Flutter混合开发(Hybird)

    因为笔者本身主要从事是Android开发,所以很多角度都是作为一个Android开发者学习Flutter的角度出发,IOS或者H5的开发同学可以选择性阅读 目录 前言 如果我们目前的项目是Androi ...

  4. 在Flutter中嵌入Native组件的正确姿势

    引言 在漫长的从Native向Flutter过渡的混合工程时期,要想平滑地过渡,在Flutter中使用Native中较为完善的控件会是一个很好的选择.本文希望向大家介绍AndroidView的使用方式 ...

  5. Native与H5交互的那些事

    前言 Hybrid开发模式目前几乎每家公司都有涉及和使用,这种开发模式兼具良好的Native用户交互体验的优势与WebApp跨平台的优势,而这种模式,在Android中必然需要WebView作为载体来 ...

  6. 【Flutter】Flutter 混合开发 ( 关联 Android 工程与 Flutter 工程 | 安卓页面中嵌入 Flutter 页面 | 安卓中启动 Flutter 页面 )

    文章目录 前言 一.创建 Android 项目 二.关联 Android 工程与 Flutter Module 工程 1.配置 Flutter Module工程 2.配置 build.gradle 3 ...

  7. android ios web兼容,js与android iOS 交互兼容

    js与android iOS 交互兼容 在Android与js交互方面还是要比iOS方便很多,而UIWebView只暴漏出- (BOOL)webView:(UIWebView *)webView sh ...

  8. React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块...

    尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息. ...

  9. 移动端跨平台开发Flutter 与 React Native对比

    移动端跨平台开发Flutter 与 React Native 深入对比分析 2019年6月21日20:41:35 发表评论 154 views 移动端跨平台在经历数年沉浮之后,如今还能在舞台聚光灯下雀 ...

最新文章

  1. 2021年9月最新的保姆级计算机视觉学习路线
  2. 内存屏障在硬件层面的实现原理
  3. 日志库EasyLogging++学习系列(1)—— 简要介绍
  4. ITK:对多个线程上的数据进行操作以利用多核处理器
  5. 【zabbix】安装、配置agent,配置被监控端
  6. location定位_旅游英语必备单词:location
  7. shell一周学习心得
  8. 星环inceptor建表公式以及各个表的区别联系
  9. 安装VMware+linux+oracle手记
  10. sqlldr mysql_sqlldr数据导入
  11. C#上位机与三菱PLC通讯的实现步骤(图文)
  12. 解答篇:金蝶K3wise总账凭证页面查询不到科目分录核算项目
  13. 考研笔记——王道C语言
  14. png转ico 或如何制作ico文件
  15. 计算机大赛鼓励语录,鼓励参加比赛的话
  16. BMW 与 Harvester 的云与边缘之旅
  17. 如何计算芯片的ESP mac 地址
  18. 从“扫月亮”到“扫福字”,扒一扒背后的支付宝AR框架体系
  19. vscode自定义括号颜色
  20. ffmpeg 有声视频合成背景音乐(合成多声音/合成多音轨)

热门文章

  1. 周期获取Linux系统内存
  2. 视图系统CBV 和 response
  3. 走近webpack(3)--图片的处理
  4. Repeating Decimals (计算循环小数)
  5. MySQL命令行登陆,远程登陆MySQL
  6. 关系数据库SQL之基本数据查询:子查询、分组查询、模糊查询
  7. Android-Ant自动编译打包android项目 -- 2 ----签名与渠道包
  8. Activity之间切换 以及传值
  9. Ubuntu 下安装adobe flash player
  10. RTP/RTCP/RTSP协议分析(2)