Flutter插件开发指南02: 事件订阅 EventChannel

图片

视频
https://www.bilibili.com/video/BV1zj411d7k4/
前言
上一节我们讲了 Channel 通道,但是如果你是卫星定位业务,原生端主动推消息给 Flutter 这时候就要用到 EventChannel 通道了。
本节会写一个 1~50 的计数器,到 50 后自动关闭原生的消息订阅。

图片

FlutterEventChannel

图片

FlutterEventChannel 的作用是在 Flutter 平台和原生平台之间建立双向通信的桥梁。通过 FlutterEventChannel,Flutter 应用程序可以向原生平台发送事件,同时也可以接收来自原生平台的事件。
FlutterEventChannel 可以用于许多场景,例如:

传感器数据采集:许多应用程序需要从设备的传感器(如加速度计、陀螺仪、磁力计等)中获取数据。Flutter 应用程序可以通过 FlutterEventChannel 发送请求,让原生平台采集传感器数据并返回到 Flutter 应用程序中。后台任务完成通知:当应用程序在后台运行时,可能需要执行一些长时间运行的任务。Flutter 应用程序可以通过 FlutterEventChannel 向原生平台发送请求,让原生平台在后台任务完成时发送通知到 Flutter 应用程序中。音频和视频流传输:许多应用程序需要在 Flutter 应用程序和原生平台之间传输音频和视频流。Flutter 应用程序可以通过 FlutterEventChannel 向原生平台发送请求,让原生平台传输音频和视频流并返回到 Flutter 应用程序中。这使得 Flutter 应用程序可以使用原生平台的音频和视频处理功能,以提高应用程序的性能和用户体验。
FlutterEventChannel 执行过程如下:

Flutter 应用程序创建一个 FlutterEventChannel 对象,并指定一个唯一的通道名称。Flutter 应用程序调用 FlutterEventChannel 的 receiveBroadcastStream 方法,以获取一个 Stream 对象,以便监听来自原生平台的事件。原生平台创建一个 EventChannel 对象,并指定与 Flutter 应用程序中通道名称相匹配的字符串。原生平台调用 EventChannel 的 setStreamHandler 方法,以设置一个 StreamHandler 对象,以便接收来自 Flutter 应用程序的事件并向其发送原生事件。当 Flutter 应用程序需要向原生平台发送事件时,它会将事件数据发送到 FlutterEventChannel 对象中。FlutterEventChannel 将事件数据传递给原生平台的 EventChannel 对象。EventChannel 对象将事件数据传递给 StreamHandler 对象中的 onListen 方法。在 onListen 方法中,原生平台可以执行一些操作并发送事件数据到 Stream 对象中。Flutter 应用程序可以通过 Stream 对象监听来自原生平台的事件,并执行相应的操作。
需要注意的是,FlutterEventChannel 中使用的 Stream 对象是异步的,因此在监听来自原生平台的事件时需要使用异步编程的技术。另外,在使用 FlutterEventChannel 时,Flutter 应用程序和原生平台之间需要约定好通道名称和事件数据格式,以便能够正确地交互和处理数据。

原文 https://ducafecat.com/blog/flutter-plugin-event-channel

参考
https://api.flutter.dev/flutter/services/EventChannel-class.html
https://mobikul.com/event-channel-in-flutter/
步骤
Flutter 插件
接口定义
lib/flutter_plugin_add_platform_interface.dart
  Future<bool?> startCounting() {    throw UnimplementedError('startCounting() has not been implemented.');  }
原生调用
lib/flutter_plugin_add_method_channel.dart
  @override  Future<bool?> startCounting() async {    final val = await methodChannel.invokeMethod<bool>('startCounting');    return val;  }
插件调用类
lib/flutter_plugin_add.dart
// 类型定义 - 接收函数typedef TypeOnRecvData = void Function(int value);
  // event channel 定义  static const eventChannel =      EventChannel('com.ducafecat.counter/eventChannel');  // 订阅  StreamSubscription? _streamSubscription;  // 接收函数  TypeOnRecvData? _onRecvData;
  // 开始计数  Future<void> startCounting(TypeOnRecvData onRecvData) async {    _onRecvData = onRecvData;    if (_streamSubscription == null) {      bool? isStarting =          await FlutterPluginAddPlatform.instance.startCounting();      if (isStarting == true) {        _streamSubscription =            eventChannel.receiveBroadcastStream().listen(_listenStream);      }    }  }
  // 取消计数  void cancelCounting() {    _streamSubscription?.cancel();    _streamSubscription = null;    _onRecvData = null;  }
  // 接收函数  void _listenStream(value) {    debugPrint("Received From Native:  $value\n");    _onRecvData?.call(value);    if (value == 50) {      cancelCounting();    }  }
  // 释放  void dispose() {    cancelCounting();  }
Flutter 例子
example/lib/main.dart
  // 计数器返回  int counterResult = 0;
  @override  void deactivate() {    // 释放    _flutterPluginAddPlugin.dispose();    super.deactivate();  }
  @override  Widget build(BuildContext context) {    ...                  // 计数 event              Text('count: $counterResult'),              ElevatedButton(                onPressed: () {                  _flutterPluginAddPlugin.startCounting((value) {                    setState(() {                      counterResult = value;                    });                  });                },                child: const Text('开始计数'),              ),              ElevatedButton(                onPressed: () {                  _flutterPluginAddPlugin.cancelCounting();                },                child: const Text('结束计数'),              ),
Android 端
android/src/main/java/com/ducafecat/flutter_plugin_add/FlutterPluginAddPlugin.java
成员变量
  // 日志标签  final String TAG_NAME = "From_Native";  // 事件通道名称  public static final String eventChannelName = "com.ducafecat.counter/eventChannel";  // 事件通道  private EventChannel.EventSink eventChannel;  // 计数器  private int count = 0;  // 事件 Handler  private Handler eventHandler;  // 消息传递器  private BinaryMessenger binaryMessenger;
保存 BinaryMessenger
  @Override  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {    binaryMessenger = flutterPluginBinding.getBinaryMessenger();
启动 onMethodCall
  @Override  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {    ...          // start    else if (call.method.equals("startCounting")) {      new EventChannel(binaryMessenger, eventChannelName).setStreamHandler(              new EventChannel.StreamHandler() {                @Override                public void onListen(Object args, final EventChannel.EventSink events) {                  Log.w(TAG_NAME, "Adding listener");                  eventChannel = events;                  count = 0;                  eventHandler = new Handler();                  runnable.run();                }                @Override                public void onCancel(Object args) {                  Log.w(TAG_NAME, "Cancelling listener");                  eventHandler.removeCallbacks(runnable);                  eventHandler = null;                  count = 0;                  eventChannel = null;                  System.out.println("StreamHandler - onCanceled: ");                }              }      );      result.success(true);    }
定时器
  private final Runnable runnable = new Runnable() {    @Override    public void run() {      int TOTAL_COUNT = 50;      if (count >= TOTAL_COUNT) {        eventChannel.endOfStream();      } else {        count++;        Log.w(TAG_NAME, "\nParsing From Native:  " + count);        eventChannel.success(count);      }      eventHandler.postDelayed(this, 200);    }  };
释放
  @Override  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {    channel.setMethodCallHandler(null);    if(eventHandler != null) {      eventChannel.endOfStream();      eventHandler.removeCallbacks(runnable);      eventHandler = null;      eventChannel = null;    }  }
IOS 端
定义成员变量
ios/Classes/FlutterPluginAddPlugin.h
// FlutterEventSink@property (nonatomic, strong) FlutterEventSink eventSink;// 定时器@property (nonatomic, strong) NSTimer *timer;// 计数器@property (nonatomic, assign) NSInteger counter;
注册 eventChannel
ios/Classes/FlutterPluginAddPlugin.m
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>)registrar { ...      // 注册事件通道    FlutterEventChannel eventChannel = [FlutterEventChannel eventChannelWithName:@"com.ducafecat.counter/eventChannel"  binaryMessenger: [registrar messenger]];    [eventChannel setStreamHandler:instance];}
方法调用
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {  ...  else if ([@"startCounting" isEqualToString:call.method]) {      result(@(YES));  }
开始订阅
- (FlutterError*)onListenWithArguments:(id)arguments                             eventSink:(FlutterEventSink)eventSink {    self.eventSink = eventSink;    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5                                                  target:self                                                selector:@selector(sendEvent)                                                userInfo:nil                                                 repeats:YES];    return nil;}
// 发送消息- (void)sendEvent {  if (self.eventSink) {      self.counter++;      self.eventSink(@(self.counter));  }}
取消订阅
- (FlutterError*)onCancelWithArguments:(id)arguments {    [self.timer invalidate];    self.timer = nil;    self.eventSink = nil;    self.counter = 0;    return nil;}
最后启动

图片

代码
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_plugin_add
小结
使用 EventChannel 可以让插件开发更加灵活、高效、可靠和易于使用,从而可以提高插件的质量和用户体验。
还需要特别注意以下几点:

线程安全性:EventChannel 的事件通信是在异步线程上执行的,因此需要确保插件代码和原生代码的线程安全性。如果在插件代码中访问 UI 线程或其他不安全的线程,可能会导致应用程序崩溃或其他问题。内存管理:在使用 EventChannel 时需要注意内存管理。如果不及时释放资源,可能会导致内存泄漏或其他性能问题。建议在 EventChannel 不再需要时,及时停止事件监听并释放资源。插件生命周期管理:在编写插件时,需要考虑插件的生命周期管理,如何在插件被加载和卸载时正确地初始化和释放资源。在 Flutter 中,可以使用 FlutterPlugin 接口中的 onAttachedToEngine 和 onDetachedFromEngine 方法来管理插件的生命周期。数据传输格式:EventChannel 传输的数据格式需要在插件和原生代码之间协商一致。建议使用标准的数据传输格式,如 JSON 或 Protocol Buffers 等。错误处理:在使用 EventChannel 时,需要考虑错误处理。如果事件通信出现错误,需要及时处理并向 Flutter 代码报告错误信息,以便及时调试和修复问题。
感谢阅读本文
如果我有什么错?请在评论中让我知道。我很乐意改进。

© 猫哥
ducafecat.com
end


独立开发者_猫哥
666 声望126 粉丝