Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel

前言

本文主要讲解,使用不同的 Channel 让 Flutter 和 Android原生 进行通信,由于只是讲解两端通信,所以可视化效果不好

不过我写了一篇专门讲解 Flutter 嵌入 Android原生View的文章

Flutter 页面嵌入 Android原生 View-CSDN博客

可以先看完,再结合案例自己改动一下,做到可视化,不过我还是建议优先看当前文章

我采用在Android原生延时发送方式,来模拟Android原生向Flutter 发送消息。

1、BasicMessageChannel

主要应用于传输数据的通道,目前提供了以下几种消息类型:

JSONMessageCodecStandardMessageCodecStringCodecBinaryCodec

默认使用的消息类型是 StandardMessageCodec

下面图片是 Flutter和Android原生,分别支持的数据类型 和 对应关系;

1.1 JSONMessageCodec

Json字符串。

1.2 StandardMessageCodec

标准类型,可以看作默认类型,支持的类型也是最多的。

1.3 StringCodec

字符串。

1.4 BinaryCodec

二进制,比如传输文件。

我挑了两个最常用的类型进行演示,比如Flutter向Android原生发送请求,Android原生收到请求后将数据响应给Flutter;

BasicMessageChannel的监听回调,没有提供具体调用了哪个函数的标识,所以在演示PUT时,我会先将GET相关代码注释,反之也一样;

如果和其他Channel配合使用可以解决这个注释问题, 比如BasicMessageChannel 和 MethodChannel 一起使用,这个案例我放在最后讲解,这一阶段先麻烦一下;

传输 BinaryCodec,我使用的是音频文件,传输完成默认开始播放,友情提示,戴耳机或者声音小点,避免社S

在Flutter添加音频播放库,库的具体地址

just_audio | Flutter Package

just_audio: ^0.9.36

Android原生 需要的配置,app/build.gradle

defaultConfig {
     ... ...
    multiDexEnabled true
}

dependencies {
    def exoplayer_version = "2.18.5"
    implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
    implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
    implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayer_version"
    implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayer_version"

    implementation 'androidx.multidex:multidex:2.0.1'
}

传输JSONMessageCodec

传输BinaryCodec

1.5 传输JSONMessageCodec

Flutter:main_json_basic_message_channel.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// BasicMessageChannel
/// 使用Map类型,对应 Android端的 JSONObject类型
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late BasicMessageChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String CHANNEL_NAME = 'flutter.mix.android/json_basic_message_channel';

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {
    channel = const BasicMessageChannel(CHANNEL_NAME,JSONMessageCodec()); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    channel.setMessageHandler(handler);
  }

  /// 监听来自 Android端 的消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> handler(dynamic message) async {
    // PUT
    var androidCount = message['androidNum'];
    msgState = 'Flutter端接收Android端PUT请求成功,数据:$androidCount';
    setState(() {});
    return 0; // 返回给Android端

    // GET,这里模拟在Android端显示
    // var randomV = getRandomV();
    // Map<String, int> map = {'flutterNum': randomV};
    // msgState = 'Flutter端接收Android端GET请求成功:$randomV';
    // setState(() {});
    // return map; // 返回给Android端
  }

  /// Flutter端 向 Android端 发送数据,PUT 操作
  flutterSendAndroidData() {
    var randomV = getRandomV();
    Map<String, int> map = {'flutterNum': randomV};

    // Android端调用Reply相关回调函数后,then、catchError 会接收到

    channel.send(map).then((value) {
      var flutterNum = value['flutterNum'];
      msgState = 'Android端接收Flutter端PUT请求成功,数据:$flutterNum ----> 5秒后,Android端会向Flutter端发送PUT请求';
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('flutterSendAndroidDataNotice --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('flutterSendAndroidDataNotice --- Error:$e');
      }
    });

  }

  ///  Flutter端 获取 Android端 数据,GET 操作
  flutterGetAndroidData() {

    // Android端调用Reply相关回调函数后,then、catchError 会接收到

    channel.send(null).then((value) {
      var androidCount = value['androidNum'];
      msgState = 'Android端接收Flutter端GET请求成功,数据:$androidCount ----> 5秒后,Android端会向Flutter端发送GET请求';
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('flutterGetAndroidDataNotice --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('flutterGetAndroidDataNotice --- Error:$e');
      }
    });
  }

  /// 获取随机数
  int getRandomV() {
    return Random().nextInt(100); // 随机数范围(0-99)
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
      fontSize: 16,
      color: Colors.orangeAccent,
      fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    width: 300,
                    child: Text(
                        msgState,
                        textAlign: TextAlign.center,
                        style: defaultStyle)
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: ElevatedButton(
                    onPressed: flutterSendAndroidData,
                    child: const Text('Flutter端向Android端发送数据'),
                  ),
                ),
                ElevatedButton(
                  onPressed: flutterGetAndroidData,
                  child: const Text('Flutter端获取Android端数据'),
                ),
              ],
            ),
          ),
        ));
  }

}

Android原生:TestJsonBasicMessageChannel.kt

package com.example.flutter_android_channel.channel

import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryCodec
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.JSONMessageCodec
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.StringCodec
import org.json.JSONException
import org.json.JSONObject

/**
 * BasicMessageChannel
 *
 * 使用 JSONObject类型,对应 Flutter端的 Map类型
 */
class TestJsonBasicMessageChannel(messenger: BinaryMessenger) :
    BasicMessageChannel.MessageHandler<Any> {

    private lateinit var mChannel: BasicMessageChannel<Any>

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val CHANNEL_NAME = "flutter.mix.android/json_basic_message_channel"
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = BasicMessageChannel(messenger, CHANNEL_NAME, JSONMessageCodec.INSTANCE)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setMessageHandler(this)
    }

    // ========================== PUT 操作 ==========================

    /**
     * 监听来自 Flutter端 的消息通道
     *
     * message: Android端 接收到 Flutter端 发来的 数据对象
     * reply:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any>) {
        // 回调结果对象
        // 获取Flutter端传过来的数据
        val flutterCount = getMap(message.toString())?.get("flutterNum")
        Log.d("TAG", "flutterCount:$flutterCount")

        // 回调状态接口对象,里面只有一个回调方法
        // reply.reply(@Nullable T reply)
        reply.reply(message) // 返回给Flutter端

        Handler(Looper.getMainLooper()).postDelayed({
            androidSendFlutterData()
        }, 5000)
    }

    /**
     * Android端 向 Flutter端 发送数据
     */
    private fun androidSendFlutterData() {
        val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
        map["androidNum"] = getRandomV() // 随机数范围(0-99)

        mChannel.send(map, object : BasicMessageChannel.Reply<Any> {

            override fun reply(reply: Any?) {
                // 获取Flutter端传过来的数据
                Log.d("TAG", "reply:$reply")
            }

        })
    }

    // ========================== GET 操作 ==========================

//    /**
//     * 监听来自 Flutter端 的消息通道
//     *
//     * message: Android端 接收到 Flutter端 发来的 数据对象
//     * reply:Android端 给 Flutter端 执行回调的接口对象
//     */
//    override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any>) {
//        val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
//        map["androidNum"] = getRandomV() // 随机数范围(0-99)
//        reply.reply(map) // 返回给Flutter端
//
//        Handler(Looper.getMainLooper()).postDelayed({
//            androidGetFlutterData()
//        }, 5000)
//    }
//
//    /**
//     * Android端 获取 Flutter端 数据
//     */
//    private fun androidGetFlutterData() {
//        mChannel.send(null, object : BasicMessageChannel.Reply<Any> {
//
//            override fun reply(reply: Any?) {
//                // 获取Flutter端传过来的数据
//                val flutterCount = getMap(reply.toString())?.get("flutterNum")
//                Log.d("TAG", "flutterCount:$flutterCount")
//            }
//
//        })
//    }

    /**
     * 获取随机数
     */
    private fun getRandomV() = (0..100).random()

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mChannel.setMessageHandler(null)
    }

    /**
     * Json 转 Map
     */
    private fun getMap(jsonString: String?): HashMap<String, Any>? {
        val jsonObject: JSONObject
        try {
            jsonObject = JSONObject(jsonString)
            val keyIter: Iterator<String> = jsonObject.keys()
            var key: String
            var value: Any
            var valueMap = HashMap<String, Any>()
            while (keyIter.hasNext()) {
                key = keyIter.next()
                value = jsonObject[key] as Any
                valueMap[key] = value
            }
            return valueMap
        } catch (e: JSONException) {
            e.printStackTrace()
        }
        return null
    }
}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestJsonBasicMessageChannel
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

    private lateinit var testJsonBasicMessageChannel: TestJsonBasicMessageChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        testJsonBasicMessageChannel = TestJsonBasicMessageChannel(flutterEngine.dartExecutor.binaryMessenger)
    }

    override fun onDestroy() {
        super.onDestroy()
        testJsonBasicMessageChannel.closeChannel()
    }

}

1.6 传输BinaryCodec

Flutter:main_byte_basic_message_channel.dart


import 'package:just_audio/just_audio.dart';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// BasicMessageChannel
/// 使用ByteData类型,对应 Android端的 ByteBuffer类型
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late BasicMessageChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String CHANNEL_NAME = 'flutter.mix.android/byte_basic_message_channel';

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {
    channel = const BasicMessageChannel(CHANNEL_NAME,BinaryCodec()); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    channel.setMessageHandler(handler);
  }

  /// 监听来自 Android端 的消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> handler(dynamic message) async {
    // PUT
    var data = message as ByteData;
    loadMusic(data);
    msgState = 'Flutter端接收Android端PUT请求成功,音频加载完毕,开始播放';
    setState(() {});
    return ByteData.view(Uint8List(0).buffer); // 返回给Android端

    // GET,这里模拟在Android端播放音乐
    // final data = await rootBundle.load('assets/music/di_jia_a.mp3');
    // loadMusic(data);
    // msgState = 'Flutter端接收Android端GET请求成功,音频加载完毕,开始播放';
    // setState(() {});
    // return data; // 返回给Android端
  }

  /// Flutter端 向 Android端 发送数据,PUT 操作
  flutterSendAndroidData() async {
    final byteData = await rootBundle.load('assets/music/di_jia_a.mp3');

    // Android端调用Reply相关回调函数后,then、catchError 会接收到

    channel.send(byteData).then((value) {
      loadMusic(value);
      msgState = 'Android端接收Flutter端PUT请求成功,音频加载完毕,开始播放 --- 5秒钟后 Android端会向Flutter端发送PUT请求';
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('flutterSendAndroidDataNotice --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('flutterSendAndroidDataNotice --- Error:$e');
      }
    });
  }

  ///  Flutter端 获取 Android端 数据,GET 操作
  flutterGetAndroidData() {

    // Android端调用Reply相关回调函数后,then、catchError 会接收到

    channel.send(null).then((value) {
      loadMusic(value);
      msgState = 'Android端接收Flutter端GET请求成功,音频加载完毕,开始播放 --- 5秒钟后 Android端会向Flutter端发送GET请求';
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('flutterGetAndroidDataNotice --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('flutterGetAndroidDataNotice --- Error:$e');
      }
    });

  }

  final player = AudioPlayer();

  /// 加载音频
  loadMusic(ByteData data) async {
    var buffer = data.buffer;
    var uint8list = buffer.asUint8List(data.offsetInBytes,data.lengthInBytes);
    var audioSource = AudioSource.uri(Uri.dataFromBytes(uint8list));
    await player.setAudioSource(audioSource);
    player.play(); // 播放音乐
  }

  /// 播放或暂停
  palsyOrPause() {
    if(player.playing) {
      player.pause();
    }
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
      fontSize: 16,
      color: Colors.orangeAccent,
      fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    width: 300,
                    child: Text(
                        msgState,
                        textAlign: TextAlign.center,
                        style: defaultStyle)
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: ElevatedButton(
                    onPressed: flutterSendAndroidData,
                    child: const Text('Flutter端向Android端发送数据'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: ElevatedButton(
                    onPressed: flutterGetAndroidData,
                    child: const Text('Flutter端获取Android端数据'),
                  ),
                ),
                ElevatedButton(
                  onPressed: palsyOrPause,
                  child: Text('暂停'),
                ),
              ],
            ),
          ),
        ));
  }

}

Android原生:TestByteBasicMessageChannel.kt

package com.example.flutter_android_channel.channel

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.common.io.ByteStreams
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryCodec
import io.flutter.plugin.common.BinaryMessenger
import java.nio.ByteBuffer
import java.nio.ByteOrder

/**
 * BasicMessageChannel
 *
 * 使用 ByteBuffer类型,对应 Flutter端的 ByteData类型
 */
class TestByteBasicMessageChannel(messenger: BinaryMessenger, private val context: Context) :
    BasicMessageChannel.MessageHandler<ByteBuffer> {

    private lateinit var mChannel: BasicMessageChannel<ByteBuffer>

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val CHANNEL_NAME = "flutter.mix.android/byte_basic_message_channel"
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = BasicMessageChannel(messenger, CHANNEL_NAME, BinaryCodec.INSTANCE)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setMessageHandler(this)
    }

    // ========================== PUT 操作 ==========================

    /**
     * 监听来自 Flutter端 的消息通道
     *
     * byteBuffer: Android端 接收到 Flutter端 发来的 数据对象
     * reply:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMessage(byteBuffer: ByteBuffer?, reply: BasicMessageChannel.Reply<ByteBuffer>) {
        // 回调结果对象
        // 获取Flutter端传过来的数据
        Log.d("TAG", "byteBuffer:$byteBuffer")

        // 回调接口对象,里面只有一个回调方法
        // reply.reply(@Nullable T reply)
        byteBuffer?.order(ByteOrder.nativeOrder())
        val direct = ByteBuffer.allocateDirect(byteBuffer!!.capacity())
        direct.put(byteBuffer)
        reply.reply(direct) // 返回给Flutter端

        Handler(Looper.getMainLooper()).postDelayed({
            androidSendFlutterData()
        }, 5000)
    }

    /**
     * Android端 向 Flutter端 发送数据
     */
    private fun androidSendFlutterData() {
        // 读取assert目录下的音频文件
        val fileInputStream = context.assets.open("music/di_jia_b.mp3")
        val targetArray = ByteStreams.toByteArray(fileInputStream)
        val byteBuffer = ByteBuffer.wrap(targetArray)

        byteBuffer.order(ByteOrder.nativeOrder())
        val direct = ByteBuffer.allocateDirect(byteBuffer.capacity())
        direct.put(byteBuffer)

        mChannel.send(direct,object : BasicMessageChannel.Reply<ByteBuffer> {

            override fun reply(reply: ByteBuffer?) {
                Log.d("TAG", "reply:$reply")
            }

        })
    }

    // ========================== GET 操作 ==========================

//    /**
//     * 监听来自 Flutter端 的消息通道
//     *
//     * byteBuffer: Android端 接收到 Flutter端 发来的 数据对象
//     * reply:Android端 给 Flutter端 执行回调的接口对象
//     */
//    override fun onMessage(byteBuffer: ByteBuffer?, reply: BasicMessageChannel.Reply<ByteBuffer>) {
//        // 读取assert目录下的音频文件
//        val fileInputStream = context.assets.open("music/di_jia_b.mp3")
//        val targetArray = ByteStreams.toByteArray(fileInputStream)
//        val byteBuffer = ByteBuffer.wrap(targetArray)
//
//        byteBuffer.order(ByteOrder.nativeOrder())
//        val direct = ByteBuffer.allocateDirect(byteBuffer.capacity())
//        direct.put(byteBuffer)
//        reply.reply(direct) // 返回给Flutter端
//
//        Handler(Looper.getMainLooper()).postDelayed({
//            androidGetFlutterData()
//        }, 5000)
//    }
//
//    /**
//     * Android端 获取 Flutter端 数据
//     */
//    private fun androidGetFlutterData() {
//        mChannel.send(null,object : BasicMessageChannel.Reply<ByteBuffer> {
//
//            override fun reply(reply: ByteBuffer?) {
//                // 获取Flutter端传过来的数据
//                Log.d("TAG", "reply:$reply")
//            }
//
//        })
//    }

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mChannel.setMessageHandler(null)
    }

}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestByteBasicMessageChannel
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

    private lateinit var testByteBasicMessageChannel: TestByteBasicMessageChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)        
        testByteBasicMessageChannel = TestByteBasicMessageChannel(flutterEngine.dartExecutor.binaryMessenger,this)        
    }

    override fun onDestroy() {
        super.onDestroy()
        testByteBasicMessageChannel.closeChannel()
    }

}

2、MethodChannel

主要应用于Flutter和Android原生之间函数相互调用,所以它提供methodName作为具体调用函数的标识,就不用像演示 BasicMessageChannel那样,需要注释代码。

默认使用的消息类型是 StandardMessageCodec

Flutter:main_method_channel.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// MethodChannel
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late MethodChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String CHANNEL_NAME = 'flutter.mix.android/method_channel';
  static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 发送数据
  static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 获取 Android端 数据
  static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据
  static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 获取 Flutter端 数据

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {
    channel = const MethodChannel(CHANNEL_NAME); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    channel.setMethodCallHandler(handler);
  }

  /// 监听来自 Android端 的消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> handler(MethodCall call) async {
    // 获取调用函数的名称
    final String methodName = call.method;
    switch (methodName) {
      case ANDROID_SEND_FLUTTER_DATA_NOTICE:
        {
          int androidCount = call.arguments['androidNum'];
          msgState = 'Flutter端接收Android端PUT请求成功,数据:$androidCount';
          setState(() {});

          return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success'; // 返回给Android端
        }
      case ANDROID_GET_FLUTTER_DATA_NOTICE:
      {
        msgState = 'Flutter端接收Android端GET请求成功,返回数据:${getRandomV()}';
        setState(() {});

        return '$ANDROID_GET_FLUTTER_DATA_NOTICE ---> success:${getRandomV()}'; // 返回给Android端
      }
      default:
        {
          return PlatformException(code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端
        }
    }
  }

  /// Flutter端 向 Android端 发送数据,PUT 操作
  flutterSendAndroidData() {
    var randomV = getRandomV();
    Map<String, int> map = {'flutterNum': randomV};

    // Android端调用Result相关回调函数后,then、catchError 会接收到

    channel.invokeMethod(FLUTTER_SEND_ANDROID_DATA_NOTICE, map).then((value) {
      msgState = value;
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });

  }

  ///  Flutter端 获取 Android端 数据,GET 操作
  flutterGetAndroidData() {

    // Android端调用Result相关回调函数后,then、catchError 会接收到

    channel.invokeMethod(FLUTTER_GET_ANDROID_DATA_NOTICE).then((value) {
      msgState = value;
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });

  }

  /// 获取随机数
  int getRandomV() {
    return Random().nextInt(100); // 随机数范围(0-99)
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
        fontSize: 16,
        color: Colors.orangeAccent,
        fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    width: 300,
                    child: Text(
                      msgState,
                      textAlign: TextAlign.center,
                      style: defaultStyle)
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: ElevatedButton(
                    onPressed: flutterSendAndroidData,
                    child: const Text('Flutter端向Android端发送数据'),
                  ),
                ),
                ElevatedButton(
                  onPressed: flutterGetAndroidData,
                  child: const Text('Flutter端获取Android端数据'),
                ),
              ],
            ),
          ),
        ));
  }

}

Android原生:TestMethodChannel.kt

package com.example.flutter_android_channel.channel

import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.random.Random

/**
 * MethodChannel
 */
class TestMethodChannel(messenger: BinaryMessenger) : MethodChannel.MethodCallHandler {

    private lateinit var mChannel: MethodChannel

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val CHANNEL_NAME = "flutter.mix.android/method_channel"
        private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据
        private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 获取 Flutter端 数据
        private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 发送数据
        private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 获取 Android端 数据
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = MethodChannel(messenger, CHANNEL_NAME)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setMethodCallHandler(this)
    }

    /**
     * 监听来自 Flutter端 的消息通道
     *
     * call: Android端 接收到 Flutter端 发来的 数据对象
     * result:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        // 获取调用函数的名称
        val methodName: String = call.method
        when (methodName) {
            FLUTTER_SEND_ANDROID_DATA_NOTICE -> {
                // 回调结果对象
                // 获取Flutter端传过来的数据
                val flutterCount: Int? = call.argument<Int>("flutterNum")
                result.success("Android端接收Flutter端PUT请求成功,数据:$flutterCount ----> 5秒后,Android端会向Flutter端发送PUT请求")

                Handler(Looper.getMainLooper()).postDelayed({
                    androidSendFlutterData()
                }, 5000)

                // 回调状态接口对象,里面有三个回调方法,都可以给Flutter端返回消息
                // result.success(result: Any?)
                // result.error(errorCode: String, errorMessage: String?, errorDetails: Any?)
                // result.notImplemented()
            }

            FLUTTER_GET_ANDROID_DATA_NOTICE -> {
                result.success("Android端接收Flutter端GET请求成功,返回数据:${getRandomV()} ----> 5秒后,Android端会向Flutter端发送GET请求")

                Handler(Looper.getMainLooper()).postDelayed({
                    androidGetFlutterData()
                }, 5000)
            }

            else -> {
                result.notImplemented()
            }
        }
    }

    /**
     * Android端 向 Flutter端 发送数据,相当于 PUT 操作
     */
    private fun androidSendFlutterData() {
        val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
        map["androidNum"] = getRandomV() // 随机数范围(0-99)

        mChannel.invokeMethod(ANDROID_SEND_FLUTTER_DATA_NOTICE, map, object : MethodChannel.Result {
            override fun success(result: Any?) {
                Log.d("TAG", "$result")
            }

            override fun error(
                errorCode: String,
                errorMessage: String?,
                errorDetails: Any?
            ) {
                Log.d(
                    "TAG", "errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
                )
            }

            /**
             * Flutter端 未实现 Android端 定义的接口方法
             */
            override fun notImplemented() {
                Log.d("TAG", "notImplemented")
            }
        })
    }

    /**
     * Android端 获取 Flutter端 数据,相当于 GET 操作
     */
    private fun androidGetFlutterData() {
        // 说一个坑,不传参数可以写null,
        // 但不能这样写,目前它没有这个重载方法,invokeMethod第二个参数是Object类型,所以编译器不会提示错误
        // mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, object : MethodChannel.Result {

        // public void invokeMethod(@NonNull String method, @Nullable Object arguments)

        mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, null, object : MethodChannel.Result {
            override fun success(result: Any?) {
                Log.d("TAG", "$result")
            }

            override fun error(
                errorCode: String,
                errorMessage: String?,
                errorDetails: Any?
            ) {
                Log.d(
                    "TAG", "errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
                )
            }

            /**
             * Flutter端 未实现 Android端 定义的接口方法
             */
            override fun notImplemented() {
                Log.d("TAG", "notImplemented")
            }
        })
    }

    /**
     * 获取随机数
     */
    private fun getRandomV() = (0..100).random()

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mChannel.setMethodCallHandler(null)
    }

}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestMethodChannel
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

TestByteBasicMessageChannel
    private lateinit var testMethodChannel: TestMethodChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        testMethodChannel = TestMethodChannel(flutterEngine.dartExecutor.binaryMessenger)
    }

    override fun onDestroy() {
        super.onDestroy()
        testMethodChannel.closeChannel()
    }

}

3、EventChannel

主要应用于 原生端 向 Flutter 单向通信

默认使用的消息类型是 StandardMessageCodec

Flutter:main_single_event_channel.dart


import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// EventChannel
/// 使用方式:单向通信
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late EventChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String CHANNEL_NAME = 'flutter.mix.android/single_event_channel';

  StreamSubscription? streamSubscription;

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {
    channel = const EventChannel(CHANNEL_NAME); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    streamSubscription = channel
        .receiveBroadcastStream()
        .listen(onData, onError: onError, onDone: onDone);
  }

  /// 监听来自 Android端 的消息通道
  /// 这几个函数就会根据情况被触发

  /// 响应数据
  onData(dynamic data) {
    msgState = data;
    setState(() {});
  }

  /// 发生异常
  onError(dynamic error) {
    msgState = error;
    setState(() {});
  }

  /// 流被关闭
  onDone() {
    msgState = "流被关闭";
    setState(() {});
  }

  @override
  void dispose() {
    super.dispose();
    streamSubscription?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
      fontSize: 50,
      color: Colors.orangeAccent,
      fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    width: 300,
                    child: Text(msgState,
                        textAlign: TextAlign.center, style: defaultStyle)),
              ],
            ),
          ),
        ));
  }
}

Android原生:TestSingleEventChannel.kt

package com.example.flutter_android_channel.channel

import android.os.CountDownTimer
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel

/**
 * EventChannel
 *
 * 使用方式:单向通信
 */
class TestSingleEventChannel(messenger: BinaryMessenger) : EventChannel.StreamHandler {

    private lateinit var mChannel: EventChannel

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val CHANNEL_NAME = "flutter.mix.android/single_event_channel"
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = EventChannel(messenger, CHANNEL_NAME)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setStreamHandler(this)
    }

    private var count: Int = 10

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {

        // 一共10秒,每隔1秒执行一次
        object : CountDownTimer(10000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                // 还剩下多少秒,依次为2000、1000、0
                if (millisUntilFinished == 0L) {
                    cancel()
                }
                events?.success("${count--}")
            }

            override fun onFinish() { // 结束后的操作
                events?.success("${count--}")
            }
        }.start()

        // 给Flutter端返回消息
        // events?.endOfStream()
        // events?.success(event: Any?)
        // events?.error(errorCode: String?, errorMessage: String?, errorDetails: Any?)
        // events?.endOfStream() // 流结束
    }

    override fun onCancel(arguments: Any?) {

    }

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mChannel.setStreamHandler(null)
    }

}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestSingleEventChannel
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

    private lateinit var testSingleEventChannel : TestSingleEventChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        testSingleEventChannel = TestSingleEventChannel(flutterEngine.dartExecutor.binaryMessenger)
    }

    override fun onDestroy() {
        super.onDestroy()
        testSingleEventChannel.closeChannel()
    }

}

怀疑

  • 它用到Stream,流在开发中,代表一连串数据,主要应用在传输文件,比如IO流;
  • EventChannel.EventSink,它提供的那些函数名,一看就是返回操作,既然是返回,就需要前置条件,比如接收到请求;
  • BinaryMessenger.send(String channel, ByteData? message),我找到了方法,传输的数据类型是ByteData,结合前两项,逻辑通了,那是否意味着Flutter可以使用EventChannel主动向原生端发送请求

1.0 撸起袖子,开始干

Flutter一切正常,成功发送数据,且Android原生也成功接收到数据,但遇到了解析异常;

17:53:44.737  E  Uncaught exception in binary message listener
                 java.lang.IllegalArgumentException: Message corrupted
                 	at io.flutter.plugin.common.StandardMessageCodec.readValueOfType(StandardMessageCodec.java:450)
                 	at io.flutter.plugin.common.StandardMessageCodec.readValue(StandardMessageCodec.java:340)
                 	at io.flutter.plugin.common.StandardMethodCodec.decodeMethodCall(StandardMethodCodec.java:48)
                 	at io.flutter.plugin.common.EventChannel$IncomingStreamRequestHandler.onMessage(EventChannel.java:195)
                 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
                 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322)
                 	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12)
                 	at android.os.Handler.handleCallback(Handler.java:942)
                 	at android.os.Handler.dispatchMessage(Handler.java:99)
                 	at android.os.Looper.loopOnce(Looper.java:201)
                 	at android.os.Looper.loop(Looper.java:288)
                 	at android.app.ActivityThread.main(ActivityThread.java:7872)
                 	at java.lang.reflect.Method.invoke(Native Method)
                 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

1.1 异常分析

这个异常是在原生端触发的,默认使用的消息类型是 StandardMessageCodec,它的父类是MethodCodec抽象类,目前没有可以解析 ByteBuffer的子类,这就很矛盾,发送成功,接收成功,但不支持解析,于是我抱着侥幸的心态,跑去问官方,看看是不是我使用的方式不对。

StandardMessageCodec.java 中的数据解析方法;

 @Nullable
  protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
    final Object result;
    switch (type) {
      case NULL:
        result = null;
        break;
      case TRUE:
        result = true;
        break;
      case FALSE:
        result = false;
        break;
      case INT:
        result = buffer.getInt();
        break;
      case LONG:
        result = buffer.getLong();
        break;
      case BIGINT:
        {
          final byte[] hex = readBytes(buffer);
          result = new BigInteger(new String(hex, UTF8), 16);
          break;
        }
      case DOUBLE:
        readAlignment(buffer, 8);
        result = buffer.getDouble();
        break;
      case STRING:
        {
          final byte[] bytes = readBytes(buffer);
          result = new String(bytes, UTF8);
          break;
        }
      case BYTE_ARRAY:
        {
          result = readBytes(buffer);
          break;
        }
      case INT_ARRAY:
        {
          final int length = readSize(buffer);
          final int[] array = new int[length];
          readAlignment(buffer, 4);
          buffer.asIntBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 4 * length);
          break;
        }
      case LONG_ARRAY:
        {
          final int length = readSize(buffer);
          final long[] array = new long[length];
          readAlignment(buffer, 8);
          buffer.asLongBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 8 * length);
          break;
        }
      case DOUBLE_ARRAY:
        {
          final int length = readSize(buffer);
          final double[] array = new double[length];
          readAlignment(buffer, 8);
          buffer.asDoubleBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 8 * length);
          break;
        }
      case LIST:
        {
          final int size = readSize(buffer);
          final List<Object> list = new ArrayList<>(size);
          for (int i = 0; i < size; i++) {
            list.add(readValue(buffer));
          }
          result = list;
          break;
        }
      case MAP:
        {
          final int size = readSize(buffer);
          final Map<Object, Object> map = new HashMap<>();
          for (int i = 0; i < size; i++) {
            map.put(readValue(buffer), readValue(buffer));
          }
          result = map;
          break;
        }
      case FLOAT_ARRAY:
        {
          final int length = readSize(buffer);
          final float[] array = new float[length];
          readAlignment(buffer, 4);
          buffer.asFloatBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 4 * length);
          break;
        }
      default:
        throw new IllegalArgumentException("Message corrupted");
    }
    return result;
  }

1.2 Flutter官方回复

这是Git问题地址:https://github.com/flutter/flutter/issues/141876

这是回复,我翻译成中文。

问题:那么EventChannel只能用于与flutter进行原生单向通信吗?

答复:是的。

问题:如果是这样,为什么Flutter EventChannel提供了channel.binaryMessenger.send方法和channel.binaryMessenger.setMessageHandler监听

答复:事实并非如此; 这些是 BinaryMessenger 上的方法。 BinaryMessenger 是 EventChannel 构建的较低级别构造,这就是它采用二进制信使作为构造函数参数的原因。

问题:还是我的使用方式有问题?

答复:是的。 您试图绕过 EventChannel 抽象并发送您自己的原始二进制消息,这些消息不符合 EventChannel 使用二进制消息传递程序的方式,因此您收到的崩溃是预期的结果。

如果您想要发送自己的原始二进制消息,那么您需要使用自己的通道,而不是 EventChannel。

好了,不用纠结了,官方实锤 EventChannel 只能由 原生端 向 Flutter 单向通信

别的不说,这官方回复问题的效率是真高。

Flutter:main_bilateral_event_channel.dart


import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// EventChannel
/// 使用方式:尝试双向通信,但失败
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late EventChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String CHANNEL_NAME = 'flutter.mix.android/bilateral_event_channel';

  StreamSubscription? streamSubscription;

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {
    // channel = EventChannel(CHANNEL_NAME, CustomizeStandardMethodCodec());

    channel = const EventChannel(CHANNEL_NAME); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    streamSubscription = channel
        .receiveBroadcastStream()
        .listen(onData, onError: onError, onDone: onDone);
  }

  /// 监听来自 Android端 的消息通道
  /// 这几个函数就会根据情况被触发

  /// 响应数据
  onData(dynamic data) {
    msgState = data;
    setState(() {});
  }

  /// 发生异常
  onError(dynamic error) {
    msgState = error;
    setState(() {});
  }

  /// 流被关闭
  onDone() {
    msgState = "流被关闭";
    setState(() {});
  }

  /// Flutter端 向 Android端 发送数据,相当于 PUT 操作
  flutterSendAndroidData() async {
    final byteData = await rootBundle.load('assets/music/di_jia_a.mp3');
    channel.binaryMessenger.send(CHANNEL_NAME, byteData);
  }

  @override
  void dispose() {
    super.dispose();
    streamSubscription?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
      fontSize: 30,
      color: Colors.orangeAccent,
      fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: SizedBox(
                      width: 300,
                      child: Text(msgState,
                          textAlign: TextAlign.center, style: defaultStyle)),
                ),
                ElevatedButton(
                  onPressed: flutterSendAndroidData,
                  child: const Text('发送',style: TextStyle(fontSize: 20)),
                )
              ],
            ),
          ),
        ));
  }
}

Android原生:TestBilateralEventChannel.kt

package com.example.flutter_android_channel.channel

import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel

/**
 * EventChannel
 *
 * 使用方式:尝试双向通信,但失败
 */
class TestBilateralEventChannel(messenger: BinaryMessenger) : EventChannel.StreamHandler {

    private lateinit var mChannel: EventChannel

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val CHANNEL_NAME = "flutter.mix.android/bilateral_event_channel"
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = EventChannel(messenger, CHANNEL_NAME)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setStreamHandler(this)
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        Log.d("TAG","arguments:$arguments")
    }

    override fun onCancel(arguments: Any?) {

    }

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mChannel.setStreamHandler(null)
    }

}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestBilateralEventChannel
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

    private lateinit var testBilateralEventChannel: TestBilateralEventChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        testBilateralEventChannel = TestBilateralEventChannel(flutterEngine.dartExecutor.binaryMessenger)
    }

    override fun onDestroy() {
        super.onDestroy()
        testBilateralEventChannel.closeChannel()
    }

}

4、不同的Channel配合使用

不同的Channel针对不同的应用场景,实际开发中,根据业务需求会相互配合使用;

BasicMessageChannel 和 MethodChannel 一起配合使用,使用BasicMessageChannel 传输数据,传输完成后,调用 MethodChannel 向Flutter发送消息。

Flutter:main_mix_use_channel.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// BasicMessageChannel + MethodChannel 一起配合使用
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  late BasicMessageChannel mBasicMessageChannel;
  late MethodChannel mMethodChannel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  static const String MIX_BASIC_MESSAGE_CHANNEL_NAME = 'flutter.mix.android/mix_json_basic_message_channel';
  static const String MIX_METHOD_CHANNEL_NAME = 'flutter.mix.android/mix_method_channel';
  static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据

  String msgState = "默认"; // 消息传递状态

  @override
  initState() {
    super.initState();
    initChannel();
  }

  /// 初始化消息通道
  initChannel() {

    // 创建 Flutter端和Android端的,相互通信的通道
    mBasicMessageChannel = const BasicMessageChannel(MIX_BASIC_MESSAGE_CHANNEL_NAME,JSONMessageCodec());
    mMethodChannel = const MethodChannel(MIX_METHOD_CHANNEL_NAME);

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    mBasicMessageChannel.setMessageHandler(messageHandler);
    mMethodChannel.setMethodCallHandler(methodHandler);

  }

  /// 监听来自 Android端 的 BasicMessageChannel 消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> messageHandler(dynamic message) async {}

  /// 监听来自 Android端 的 MethodChannel 消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> methodHandler(MethodCall call) async {
    // 获取调用函数的名称
    final String methodName = call.method;
    switch (methodName) {
      case ANDROID_SEND_FLUTTER_DATA_NOTICE:
        {
          int androidCount = call.arguments['androidNum'];
          msgState = 'Flutter端接收Android端请求成功,数据:$androidCount';
          setState(() {});

          return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success'; // 返回给Android端
        }
      default:
        {
          return PlatformException(code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端
        }
    }
  }

  /// Flutter端 向 Android端 发送数据,PUT 操作
  flutterSendAndroidData() {
    var randomV = getRandomV();
    Map<String, int> map = {'flutterNum': randomV};

    // Android端调用Reply相关回调函数后,then、catchError 会接收到

    mBasicMessageChannel.send(map).then((value) {
      var flutterNum = value['flutterNum'];
      msgState = 'Android端接收Flutter端请求成功,数据:$flutterNum ----> 5秒后,Android端会向Flutter端发送请求';
      setState(() {});
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('flutterSendAndroidDataNotice --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('flutterSendAndroidDataNotice --- Error:$e');
      }
    });

  }

  /// 获取随机数
  int getRandomV() {
    return Random().nextInt(100); // 随机数范围(0-99)
  }

  @override
  Widget build(BuildContext context) {
    const defaultStyle = TextStyle(
        fontSize: 16,
        color: Colors.orangeAccent,
        fontWeight: FontWeight.bold,
    );
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: SafeArea(
          top: true,
          child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    width: 300,
                    child: Text(
                      msgState,
                      textAlign: TextAlign.center,
                      style: defaultStyle)
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: ElevatedButton(
                    onPressed: flutterSendAndroidData,
                    child: const Text('Flutter端向Android端发送数据'),
                  ),
                )
              ],
            ),
          ),
        ));
  }

}

Android原生:TestMixUseChannel.kt

package com.example.flutter_android_channel.channel

import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.JSONMessageCodec
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.json.JSONException
import org.json.JSONObject

/**
 * BasicMessageChannel + MethodChannel 一起配合使用
 *
 */
class TestMixUseChannel(messenger: BinaryMessenger) : BasicMessageChannel.MessageHandler<Any>,MethodChannel.MethodCallHandler {

    private lateinit var mBasicMessageChannel: BasicMessageChannel<Any>
    private lateinit var mMethodChannel: MethodChannel

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val MIX_BASIC_MESSAGE_CHANNEL_NAME = "flutter.mix.android/mix_json_basic_message_channel"
        private const val MIX_METHOD_CHANNEL_NAME = "flutter.mix.android/mix_method_channel"
        private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据
    }

    init {
        initChannel(messenger)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger) {

        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mBasicMessageChannel = BasicMessageChannel(messenger, MIX_BASIC_MESSAGE_CHANNEL_NAME, JSONMessageCodec.INSTANCE)
        mMethodChannel = MethodChannel(messenger, MIX_METHOD_CHANNEL_NAME)

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mBasicMessageChannel.setMessageHandler(this)
        mMethodChannel.setMethodCallHandler(this)

    }

    /**
     * 监听来自 Flutter端 的 BasicMessageChannel 消息通道
     *
     * message: Android端 接收到 Flutter端 发来的 数据对象
     * reply:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any>) {
        // 回调结果对象
        // 获取Flutter端传过来的数据
        val flutterCount = getMap(message.toString())?.get("flutterNum")
        Log.d("TAG", "flutterCount:$flutterCount")

        // 回调状态接口对象,里面只有一个回调方法
        // reply.reply(@Nullable T reply)
        reply.reply(message) // 返回给Flutter端

        Handler(Looper.getMainLooper()).postDelayed({
            androidSendFlutterData()
        }, 5000)
    }

    /**
     * 监听来自 Flutter端 的 MethodChannel 消息通道
     *
     * call: Android端 接收到 Flutter端 发来的 数据对象
     * result:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {}

    /**
     * Android端 向 Flutter端 发送数据,相当于 PUT 操作
     */
    private fun androidSendFlutterData() {
        val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
        map["androidNum"] = getRandomV() // 随机数范围(0-99)

        mMethodChannel.invokeMethod(ANDROID_SEND_FLUTTER_DATA_NOTICE, map, object : MethodChannel.Result {
            override fun success(result: Any?) {
                Log.d("TAG", "$result")
            }

            override fun error(
                errorCode: String,
                errorMessage: String?,
                errorDetails: Any?
            ) {
                Log.d(
                    "TAG", "errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
                )
            }

            /**
             * Flutter端 未实现 Android端 定义的接口方法
             */
            override fun notImplemented() {
                Log.d("TAG", "notImplemented")
            }
        })
    }

    /**
     * 解除绑定
     */
    fun closeChannel() {
        mBasicMessageChannel.setMessageHandler(null)
        mMethodChannel.setMethodCallHandler(null)
    }

    /**
     * 获取随机数
     */
    private fun getRandomV() = (0..100).random()


    /**
     * Json 转 Map
     */
    private fun getMap(jsonString: String?): HashMap<String, Any>? {
        val jsonObject: JSONObject
        try {
            jsonObject = JSONObject(jsonString)
            val keyIter: Iterator<String> = jsonObject.keys()
            var key: String
            var value: Any
            var valueMap = HashMap<String, Any>()
            while (keyIter.hasNext()) {
                key = keyIter.next()
                value = jsonObject[key] as Any
                valueMap[key] = value
            }
            return valueMap
        } catch (e: JSONException) {
            e.printStackTrace()
        }
        return null
    }

}

Android原生:MainActivity.kt

package com.example.flutter_android_channel

import com.example.flutter_android_channel.channel.TestMixUseChannel

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {

    private lateinit var testMixUseChannel: TestMixUseChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        testMixUseChannel = TestMixUseChannel(flutterEngine.dartExecutor.binaryMessenger)
    }

    override fun onDestroy() {
        super.onDestroy()
        testMixUseChannel.closeChannel()
    }

}

5、执行线程

都试了一遍,目前这些Channel都只能在主线程执行。

// Android原生

// 测试在子线程执行
Thread {
    androidSendFlutterData()
}.start()
Process: com.example.flutter_android_channel, PID: 5776
                 java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Thread-4
                 	at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:1496)
                 	at io.flutter.embedding.engine.FlutterJNI.dispatchPlatformMessage(FlutterJNI.java:1103)
                 	at io.flutter.embedding.engine.dart.DartMessenger.send(DartMessenger.java:282)
                 	at io.flutter.embedding.engine.dart.DartExecutor$DefaultBinaryMessenger.send(DartExecutor.java:470)
                 	at io.flutter.plugin.common.BasicMessageChannel.send(BasicMessageChannel.java:105)
                 	at com.example.flutter_android_channel.channel.TestJsonBasicMessageChannel.androidSendFlutterData(TestJsonBasicMessageChannel.kt:80)
                 	at com.example.flutter_android_channel.channel.TestJsonBasicMessageChannel.onMessage$lambda-1$lambda-0(TestJsonBasicMessageChannel.kt:68)
                 	at com.example.flutter_android_channel.channel.TestJsonBasicMessageChannel.$r8$lambda$vHbhJxL-HBJ37W1nuB2sJQfndKs(Unknown Source:0)
                 	at com.example.flutter_android_channel.channel.TestJsonBasicMessageChannel$$ExternalSyntheticLambda1.run(Unknown Source:2)

6、总结

BasicMessageChannel主要应用于:传输数据;

MethodChannel主要应用于:通过函数处理业务逻辑;

EventChannel主要应用于:一些只能由原生端API才能完成的操作,处理完后发送给Flutter;

双向通信:BasicMessageChannelMethodChannel

单向通信:EventChannel

7、源码地址

https://github.com/LanSeLianMa/flutter_android_channel

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/336814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【富文本编辑器实战】02 编写编辑器配置文件

编写编辑器配置文件 目录 编写编辑器配置文件前言项目结构分析项目配置菜单项配置语言配置总体配置 总结 前言 本篇文章主要内容是项目的配置文件的编写与讲解&#xff0c;包括菜单项配置、语言配置、总体配置。 项目结构分析 下图是编辑器的总体结构&#xff1a; 编辑器大致…

蓝桥杯真题(Python)每日练Day2

题目 题目分析 对于本题首先确定其数据结构为优先队列&#xff0c;即邮费最小的衣服优先寄&#xff0c;算法符合贪心算法。可以直接使用queue库的PriorityQueue方法实现优先队列。关于PriorityQueue的使用方法主要有&#xff1a; import queue q queue.Queue()# 队列 pq qu…

Django随笔

关于Django的admin 1. 在url中把 from django.contrib import admin 重新解开 把path(admin/,admin.site.urls), 解开 2. 注册app&#xff0c;在配置文件中写 django.contrib.admin, 3.输入命令进行数据库迁移 Django国际化 配置文件中&#xff08;改成中文&#xff09; LA…

云轴科技ZStack位列IDC云系统软件市场教育行业TOP2

近日&#xff0c;全球IT市场研究和咨询公司IDC发布 《中国云系统软件市场跟踪报告2023H1》 ZStack作为产品化的云基础软件提供商 位居云系统软件市场第一梯队 市场份额位列独立云厂商*第一 增速最快 教育行业TOP2 在教育行业&#xff0c;云计算已成为教育行业信息化的重要基础…

十一、常用API——爬虫

目录 爬虫本地爬虫和网络爬虫贪婪爬取和非贪婪爬取正则表达式在字符串方法中的使用捕获分组和非捕获分组分组捕获分组非捕获分组 爬虫 本地爬虫和网络爬虫 有如下文本&#xff0c;请按照要求爬取数据。&#xff08;本地爬虫&#xff09; Java自从95年问世以来&#xff0c;经历…

红队渗透靶机:TOPPO: 1

目录 信息收集 1、arp 2、nmap 3、nikto 4、whatweb 5、dirsearch WEB tips1 tips2 SSH登录 提权 系统信息收集 本地 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:69:c7:bf, IPv4: 192.168.110…

重构改善既有代码的设计-学习(一):封装

1、封装记录&#xff08;Encapsulate Record&#xff09; 一些记录性结构&#xff08;例如hash、map、hashmap、dictionary等&#xff09;&#xff0c;一条记录上持有什么字段往往不够直观。如果其使用范围比较宽&#xff0c;这个问题往往会造成许多困扰。所以&#xff0c;记录…

【LeetCode每日一题】2809. 使数组和小于等于 x 的最少时间

2024-1-19 文章目录 [2809. 使数组和小于等于 x 的最少时间](https://leetcode.cn/problems/minimum-time-to-make-array-sum-at-most-x/)思路&#xff1a; 2809. 使数组和小于等于 x 的最少时间 思路&#xff1a; 获取两个列表的长度n&#xff0c;并初始化一个二维数组f&…

深耕文档型数据库12载,SequoiaDB再开源

1月15日&#xff0c;巨杉数据库举行SequoiaDB新特性及开源项目发布活动。本次活动回顾了巨杉数据库深耕JSON文档型数据库12年的发展历程与技术演进&#xff0c;全面解读了SequoiaDB包括在高可用、安全、实时、易用性四个方向的技术特性&#xff0c;宣布了2024年面向技术社区的开…

Next-GPT: Any-to-Any Multimodal LLM

Next-GPT: Any-to-Any Multimodal LLM 最近在调研一些多模态大模型相关的论文&#xff0c;发现Arxiv上出的论文根本看不过来&#xff0c;遂决定开辟一个新坑《一页PPT说清一篇论文》。自己在读论文的过程中会用一页PPT梳理其脉络和重点信息&#xff0c;旨在帮助自己和读者快速了…

基于SpringBoot Vue养老院管理

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

使用JFLASH实现文件程序自动化合并及下载功能

主要总结下使用 SEGGER 工具集的 JFLASH 软件实现hex/bin文件合并以及程序的自动下载使用方法。 起因是最近使用到LVGL字库文件的制作&#xff0c;每次都要将分散的bin文件按既定分配的偏移作合并处理&#xff0c;刚开始使用的是二进制文件合并工具,文件少的时候还行&#xff…

【网站项目】基于jsp的199旅游景点管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

10分钟完成权限系统全流程开发

背景 首先问下chatgpt,权限系统的模型有哪些&#xff1f; 基于上述的结论&#xff0c;我们选择基于角色的访问控制(RBAC)&#xff0c;请从数据库设计、接口文档、代码实现、单元测试四个方面分别详细描述每个部份需要实现的内容。 数据库实现 针对上述的数据库设计部份&#…

【前端】WebSocket接收二进制数据转JSON并解决中文乱码问题(ArrayBuffer转json)

场景&#xff1a; WebSocket与mqtt服务器通信&#xff0c;接收二进制数据并将其转为Json使用。一般方式都会出现中文乱码问题。 解决方法&#xff1a; handleBinaryToJson(e) {let enc new TextDecoder("utf-8");let uint8_msg new Uint8Array(e);let temp en…

Python自动化实战之接口请求的实现

在前文说过&#xff0c;如果想要更好的做接口测试&#xff0c;我们要利用自己的代码基础与代码优势&#xff0c;所以该章节不会再介绍商业化的、通用的接口测试工具&#xff0c;重点介绍如何通过 python 编码来实现我们的接口测试以及通过 Pycharm 的实际应用编写一个简单接口测…

uniapp的IOS证书(.p12)和描述文件(.mobileprovision)申请 2024年最新教程

文章目录 准备环境登录 iOS Dev Center 下面我们从头开始学习一下如何申请开发证书、发布证书及相对应的描述文件。首先需要申请苹果 App ID &#xff08;App的唯一标识&#xff09;生成证书请求文件申请开发(Development)证书和描述文件申请开发(Development)证书添加调试设备…

0基础开发EtherNet/IP:协议格式,JAVA、C#、C++处理

经过一阵倒腾&#xff0c;把CIP、Ethernet/ip协议搞到手 协议的概念和理论就不提及了&#xff0c;上网随便一搜索EtherNet/IP遍地都是。 直接将协议关键点列举出来吧。 更多协议资料 www.jngbus.com 通讯软件群 30806722 这里讲解的是TCP和UDP协议的格式&#xff0c;EtherN…

Ubuntu 20.04安装yum报错:E: Unable to locate package yum

直接上解决方案&#xff01; 1、选择自己对应的版本的源地址 注意需要选择跟系统版本一致的&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ 找到Ubuntu点击小问号&#xff0c;进去选择对应的版本&#xff0c;将下面的镜像复制到Linux系统的 /etc/apt/sourc…

Elasticsearch各种高级文档操作3

本文来记录几种Elasticsearch的文档操作 文章目录 初始化文档数据聚合查询文档概述对某个字段取最大值 max 示例对某个字段取最小值 min 示例对某个字段求和 sum 示例对某个字段取平均值 avg 示例对某个字段的值进行去重之后再取总数 示例 State 聚合查询文档概述操作实例 桶聚…