Flutter如何与Native(Android)进行交互

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

前言


上一篇文章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? = null
    override 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.
   */
  @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("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函数源码如下:


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


//创建
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无法主动发起,所以就没有类似上面sendinvokeMethod函数。


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,只不过执行了几个预先定义好的函数,如listencancel。这样对MethodChannel进行再次封装,可以更简单的进行事件传递。


总结


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



目录
相关文章
|
24天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
94 4
|
2月前
|
Java Linux Android开发
移动应用开发与操作系统的交互:深入理解Android和iOS
在数字时代,移动应用成为我们日常生活的一部分。本文将深入探讨移动应用开发的核心概念、移动操作系统的工作原理以及它们如何相互作用。我们将通过实际代码示例,展示如何在Android和iOS平台上创建一个简单的“Hello World”应用,并解释其背后的技术原理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和知识。
|
25天前
|
前端开发 数据处理 Android开发
Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍
本文深入探讨了Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍,以及具体操作步骤、常见问题解决、高级调试技巧、团队协作中的调试应用和未来发展趋势,旨在帮助开发者提高调试效率,提升应用质量。
44 8
|
2月前
|
前端开发 JavaScript Android开发
Flutter 与 React Native - 详细深入对比分析(2024 年)
Flutter和React Native是两大跨平台框架,各有优缺点。Flutter性能优越,UI灵活,使用Dart;React Native生态广泛,适合JavaScript开发。
638 5
Flutter 与 React Native - 详细深入对比分析(2024 年)
|
1月前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
86 1
|
22天前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
2月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
90 7
|
3月前
|
开发框架 搜索推荐 开发工具
打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第51天】本文是一篇面向初学者的Flutter入门教程,旨在通过简单易懂的语言和实际代码示例,引导读者步入跨平台移动应用开发的世界。文章首先介绍了Flutter的基本概念和优势,然后逐步展示了如何搭建开发环境、创建第一个Flutter应用,并实现了一个简单的待办事项列表。最后,文章探讨了Flutter在实现高性能和美观界面方面的潜力,鼓励读者发挥创意,探索更多可能。
89 15
|
3月前
|
Dart 开发工具 Android开发
在 Android 系统上搭建 Flutter 环境的具体步骤是什么?
在 Android 系统上搭建 Flutter 环境的具体步骤是什么?