0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Flutter与原生Native的三种交互方式

谷歌开发者 来源:谷歌开发者 作者:谷歌开发者 2022-01-25 12:22 次阅读

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

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

1.BasicMessageChannel

2.MethodChannel

3.EventChannel

BasicMessageChannel

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

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

  public void send(@Nullable T message, @Nullable final Reply 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:

classMainActivity:FlutterActivity(){varchannel:BasicMessageChannel?= null    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {        super.configureFlutterEngine(flutterEngine)        var channel = BasicMessageChannel(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.   */  @Nullable  ByteBuffer 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.   */  @Nullable  T decodeMessage(@Nullable ByteBuffer message);}

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

当需要向 Flutter 发送消息时,执行以下代码即可:

channel?.send("androidcall")

(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 端

步骤基本一样,先创建
staticconstmessageChannel=constBasicMessageChannel("test",StringCodec());

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

设置回调:

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

发送消息:

messageChannel.send("flutter call");

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

MethodChannel

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

Android 端

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

  //创建varchannel=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 函数源码如下:

  @UiThread  public 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    @UiThread    public 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 对象,代码如下:

//创建varchannel=EventChannel(flutterEngine.dartExecutor.binaryMessenger,"test")//设置处理handlerchannel.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()        }    }
overridefunonCancel(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:(){//执行完成处理});

源码分析

我们来看一下 receiveBroadcastStream 的关键源码:

  Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) {    final MethodChannel methodChannel = MethodChannel(name, codec);    late StreamController<dynamic> controller;    controller = StreamController<dynamic>.broadcast(onListen: () async {      binaryMessenger.setMessageHandler(name, (ByteData? reply) async {...      });      try {        await methodChannel.invokeMethod<void>('listen', arguments);      } catch (exception, stack) {        ...      }    }, onCancel: () async {      binaryMessenger.setMessageHandler(name, null);      try {        await methodChannel.invokeMethod<void>('cancel', arguments);      } catch (exception, stack) {        ...      }    });    return controller.stream;  }

可以看到 EventChannel 本质上就是 MethodChannel,只不过执行了几个预先定义好的函数,如 listen 和 cancel。这样对 MethodChannel 进行再次封装,可以更简单的进行事件传递。

总结

上面我们展示了三种交互方式的使用,并解析了其内部的联系。其实可以看到三种方式最终其实都是使用了 BinaryMessenger 这一抽象类的默认实现 _DefaultBinaryMessenger。所以如果我们通过 BinaryMessenger 来实现一套自己特别的消息传递机制。

"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐

原文标题:Flutter 如何与 Native (Android) 进行交互 | 开发者说·DTalk

文章出处:【微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3911

    浏览量

    126990
  • 源码
    +关注

    关注

    8

    文章

    632

    浏览量

    29092
  • 交互
    +关注

    关注

    1

    文章

    66

    浏览量

    14783

原文标题:Flutter 如何与 Native (Android) 进行交互 | 开发者说·DTalk

文章出处:【微信号:Google_Developers,微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    车联网的三种打开方式

    目前,中国的车联网发展如何,仅从智能交互角度,如何撬动车联网的蓝海。这里分享了三种流派。
    发表于 11-27 09:48 6121次阅读

    伺服电机的三种控制方式

    伺服电机控制方式有脉冲、模拟量和通讯这三种,在不同的应用场景下,我们该如何选择伺服电机的控制方式呢?
    发表于 08-17 11:01 7026次阅读

    鸿蒙Flutter实战:05-使用第方插件

    # 鸿蒙Flutter 实战:使用第方插件 在鸿蒙Flutter开发中,如果涉及到使用原生功能,就要使用插件。使用插件有两
    发表于 10-22 21:54

    鸿蒙Flutter实战:07混合开发

    # 鸿蒙Flutter实战:混合开发 鸿蒙Flutter混合开发主要有两形式。 ## 1.基于har 将flutter module打包成har包,在
    发表于 10-23 16:00

    三种复位方式比较

    三种复位方式比较
    发表于 08-16 17:31

    步进电机的三种驱动方式

    步进电机的三种驱动方式
    发表于 01-12 17:03

    请问stm32启动的三种方式是什么意思?

    请群主详细解释下这三种启动方式,看了参考资料不是很明白其意!谢谢!
    发表于 07-17 04:35

    常见的三种无线接入方式是什么?

    蓝牙无线组网的优点是什么?常见的三种无线接入方式是什么?蓝牙无线组网原理与上网方案分享
    发表于 05-26 06:33

    STM32三种启动方式是什么

    STM32三种启动方式是什么
    发表于 12-15 07:16

    压供电系统的三种运行方式

    我国低压供电系统的三种运行方式:国低压供电系统主要有三种运行方式:TN系统、TT系统、lT系统。
    发表于 05-26 17:06 9858次阅读
    压供电系统的<b class='flag-5'>三种</b>运行<b class='flag-5'>方式</b>

    伺服电机的三种控制方式该如何应用

    一般伺服都有三种控制方式:速度控制方式,转矩控制方式,位置控制方式。大多数人想知道的就是这三种
    的头像 发表于 12-14 23:12 5200次阅读

    如何应用伺服电机的三种控制方式

    一般伺服都有三种控制方式:速度控制方式,转矩控制方式,位置控制方式。大多数人想知道的就是这三种
    发表于 01-22 06:30 7次下载
    如何应用伺服电机的<b class='flag-5'>三种</b>控制<b class='flag-5'>方式</b>

    原生开发如何学习Flutter

    原生 Android/ iOS 开发介绍如何正确学习和使用 Flutter
    的头像 发表于 02-18 18:43 1685次阅读

    缩放模拟输入信号的三种方式

    缩放模拟输入信号的三种方式
    发表于 11-02 08:16 0次下载
    缩放模拟输入信号的<b class='flag-5'>三种</b><b class='flag-5'>方式</b>

    Redis实现限流的三种方式分享

    当然,限流有许多种实现的方式,Redis具有很强大的功能,我用Redis实践了三种的实现方式,可以较为简单的实现其方式
    的头像 发表于 02-22 09:52 1004次阅读