Flutter插件开发指南02: 事件订阅 EventChannel
视频
前言
上一节我们讲了 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://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