猫哥课堂 ducafecat.com
开通 VIP 会员, 观看所有视频、附件、猫哥密友、猫哥 VIP 群

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

视频

https://youtu.be/ENmTBWuhryU

前言

上一节我们讲了 Channel 通道,但是如果你是卫星定位业务,原生端主动推消息给 Flutter 这时候就要用到 EventChannel 通道了。

本节会写一个 1~50 的计数器,到 50 后自动关闭原生的消息订阅。

FlutterEventChannel

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

FlutterEventChannel 可以用于许多场景,例如:

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

FlutterEventChannel 执行过程如下:

  1. Flutter 应用程序创建一个 FlutterEventChannel 对象,并指定一个唯一的通道名称。
  2. Flutter 应用程序调用 FlutterEventChannel 的 receiveBroadcastStream 方法,以获取一个 Stream 对象,以便监听来自原生平台的事件。
  3. 原生平台创建一个 EventChannel 对象,并指定与 Flutter 应用程序中通道名称相匹配的字符串。
  4. 原生平台调用 EventChannel 的 setStreamHandler 方法,以设置一个 StreamHandler 对象,以便接收来自 Flutter 应用程序的事件并向其发送原生事件。
  5. 当 Flutter 应用程序需要向原生平台发送事件时,它会将事件数据发送到 FlutterEventChannel 对象中。
  6. FlutterEventChannel 将事件数据传递给原生平台的 EventChannel 对象。
  7. EventChannel 对象将事件数据传递给 StreamHandler 对象中的 onListen 方法。
  8. 在 onListen 方法中,原生平台可以执行一些操作并发送事件数据到 Stream 对象中。
  9. 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

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;
void deactivate() { // 释放 _flutterPluginAddPlugin.dispose(); super.deactivate(); }
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() { 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 (nonatomic, strong) FlutterEventSink eventSink; // 定时器 (nonatomic, strong) NSTimer *timer; // 计数器 (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 可以让插件开发更加灵活、高效、可靠和易于使用,从而可以提高插件的质量和用户体验。

还需要特别注意以下几点:

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

感谢阅读本文

如果我有什么错?请在评论中让我知道。我很乐意改进。


© 猫哥 ducafecat.com

end


Copyright 2024 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3