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

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

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



视频


https://www.bilibili.com/video/BV1zj411d7k4/


前言


上一节我们讲了 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


  @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(this200);
    }
  };

释放


  @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 可以让插件开发更加灵活、高效、可靠和易于使用,从而可以提高插件的质量和用户体验。


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



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

感谢阅读本文


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




© 猫哥
ducafecat.com


end


相关文章
|
1月前
|
Dart Android开发
鸿蒙Flutter实战:05-使用第三方插件
在鸿蒙Flutter开发中,使用原生功能需借助插件。可自编原生ArkTS代码或采用第三方插件。自编代码通过PlatformView或MethodChannel实现;第三方插件需确保适配鸿蒙,否则须配置替代插件或自行开发。
58 1
鸿蒙Flutter实战:05-使用第三方插件
|
30天前
|
编解码 Dart API
鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
本文介绍了如何开发一个 Flutter 鸿蒙插件,实现 Flutter 与鸿蒙的混合开发及双端消息通信。通过定义 `MethodChannel` 实现 Flutter 侧的 token 存取方法,并在鸿蒙侧编写 `EntryAbility` 和 `ForestPlugin`,使用鸿蒙的首选项 API 完成数据的读写操作。文章还提供了注意事项和参考资料,帮助开发者更好地理解和实现这一过程。
56 0
flutter-barrage-craft — 能成为pub.dev中最好用的弹幕插件吗🤔?
Hi👋,最近我开发了一个弹幕插件,想知道它是否有成为 pub.dev 中最好用的弹幕插件的潜力。能帮我评估一下吗?🐱‍🏍
|
3月前
|
JavaScript 前端开发 Android开发
Flutter笔记:关于WebView插件的用法(下)
Flutter笔记:关于WebView插件的用法(下)
215 5
|
3月前
|
存储 缓存 JavaScript
Flutter笔记:关于WebView插件的用法(上)
Flutter笔记:关于WebView插件的用法(上)
1478 4
|
4月前
|
Android开发
Flutter-发布插件到pub上传不上问题
Flutter-发布插件到pub上传不上问题
39 0
|
4月前
|
Dart Android开发 iOS开发
flutter插件开发
flutter插件开发
|
6月前
|
Go Docker 索引
Flutter 插件站新升级: 加入优秀 GitHub 开源项目
这几天晚上抽空把 Flutter 插件站升级,现在支持插件搜索,并收录了众多优秀的 GitHub 开源项目,让您轻松发现与插件相关的精品项目。
Flutter 插件站新升级: 加入优秀 GitHub 开源项目
|
6月前
|
Android开发
flutter插件市场,字节大牛耗时八个月又一力作
flutter插件市场,字节大牛耗时八个月又一力作
|
6月前
|
开发框架 前端开发 定位技术
【Flutter 前端技术开发专栏】Flutter 中的插件市场与开源资源利用
【4月更文挑战第30天】Flutter插件市场和开源资源加速开发进程。pub.dev是官方插件库,提供大量第三方插件,节约时间和保证质量。选择插件时关注功能需求、评价及维护状况。开源资源作为学习、解决问题和创新的平台,需注意版权、代码质量和兼容性。案例分析展示插件应用,开源社区促进交流与技术进步,未来市场将持续发展。善用资源,提升开发效率与项目竞争力。
163 0
【Flutter 前端技术开发专栏】Flutter 中的插件市场与开源资源利用