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

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

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】数据类型——MySQL的数据类型分类、数值类型、小数类型、字符串类型

文章目录 MySQL数据类型1. 数值类型1.1 tinyint类型1.2 bit类型1.3 小数类型1.3.1 float1.3.2 decimal 2.字符串类型2.1 char2.2 varchar2.3 char和varchar比较2.4 日期和时间类型2.5 enum和set MySQL 数据类型 MySQL 是一个流行的开源关系型数据库管理系统。它支持多种数据类型…

Android studio 下的APK打包失败问题解决办法

嗨&#xff0c;各位小伙伴们&#xff0c;我是你们的好朋友咕噜铁蛋&#xff01;作为移动应用开发者&#xff0c;在使用Android Studio进行APK打包时&#xff0c;有时候可能会遇到各种问题导致打包失败&#xff0c;这给我们的开发工作带来了一定的挑战。今天&#xff0c;我将和大…

linux系统监控prometheus关联Grafana展示数据

prometheus关联Grafana 前提寻找插件创建数据源输入id&#xff0c;点击右边load选择数据源查看页面 前提 安装Prometheus&#xff0c;以及需要被监控的插件 安装Grafana寻找插件 创建数据源 输入id&#xff0c;点击右边load id查看网站&#xff1a;https://grafana.com/grafa…

从零开始学逆向:理解ret2syscall

1.题目信息 链接&#xff1a;https://pan.baidu.com/s/19ymHlZZmVGsJHFmmlwww0w 提取码&#xff1a;r4el 首先checksec 看一下保护机制 2.原理 ret2syscall 即控制程序执行系统调用来获取 shell 什么是系统调用&#xff1f; 操作系统提供给用户的编程接口是提供访问操作系统…

共基课程学习

序言 教育教师 政治基础知识 马克思主义哲学 西方哲学史 三个阶段 西方哲学的起源 圈1 圈2 圈3 第一个哲学高峰 希腊三贤 圈4

【更新】ARCGIS之成片区开发方案报备坐标txt格式批量导出工具(定制开发版)

序言 之前开发的成片区开发方案报备格式是按湖北省的标准定制的&#xff0c;目前&#xff0c;自然资源部又有了新的格式要求&#xff0c;现在新增国标版的成片区开发方案报备格式导出。 之前版本软件详见&#xff1a;软件介绍 一、软件简介 本软件是基于arcgis二次开发的工具&…

RabbitMQ(一):消息队列MQ

目录 1 消息队列MQ1.1 MQ简介1、什么是MQ2、MQ的优势流量削峰应用解耦异常处理数据分发分布式事务 3、消息中间件的弊端4、常用的MQ 1.2 MQ中几个基本概念1.3 MQ的通信模式1.4 消息的发布策略1.5 常用消息中间件协议1、AMQP协议2、MQTT协议3、OpenMessage协议4、kafaka协议 1 消…

浅谈密码学

文章目录 每日一句正能量前言什么是密码学对称加密简述加密语法Kerckhoffs原则常用的加密算法现代密码学的原则威胁模型&#xff08;按强度增加的顺序&#xff09; 密码学的应用领域后记 每日一句正能量 人生在世&#xff0c;谁也不能做到让任何人都喜欢&#xff0c;所以没必要…

抖音爬虫批量视频提取功能介绍|抖音评论提取工具

抖音爬虫是指通过编程技术从抖音平台上获取视频数据的程序。在进行抖音爬虫时&#xff0c;需要注意遵守相关法律法规和平台规定&#xff0c;以确保数据的合法获取和使用。 一般来说&#xff0c;抖音爬虫可以实现以下功能之一&#xff1a;批量视频提取。这个功能可以用于自动化地…

Kafka入门二——SpringBoot连接Kafka示例

实现 1.引入maven依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache…

软考38-上午题-【数据库】-关系模式

一、关系模式中的基本术语 关系数据库系统是支持关系模式的数据库系统。 1、关系 一个关系就是一张二维表&#xff0c;每个关系都有一个关系名。 2、元组 表中的一行即为一个元组&#xff0c;对应存储文件中的一个记录值。 3、属性 表中的列称为属性&#xff0c;每一列有一…

构建生物医学知识图谱from zero to hero (3):生物医学命名实体识别和链接

生物医学实体链接 🤓现在是激动人心的部分。对于NLP和命名实体识别和链接的新手,让我们从一些基础知识开始。命名实体识别技术用于检测文本中的相关实体或概念。例如,在生物医学领域,我们希望在文本中识别各种基因、药物、疾病和其他概念。 生物医学概念提取 在这个例子中…

《Linux C编程实战》笔记:信号量

信号量在操作系统的书里一般都有介绍&#xff0c;这里就只写书上说的了。 信号量是一个计数器&#xff0c;常用于处理进程或线程的同步问题&#xff0c;特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源&#xff0c;这里的…

删除遥感影像raster:另一个程序正在使用此文件,进程无法访问

问题&#xff1a; 在文件夹中删除处理过程得到的临时影像时&#xff0c;出现了上面的问题 os.remove(os.path.join(workspace2.replace(.tif, .cut.tif)))原因&#xff1a; 在文件夹中删除任何内容&#xff0c;比如文本、图片、影像时&#xff0c;都要先关闭这个对象 解决方…

【这个词(Sequence-to-Sequence)在深度学习中怎么解释,有什么作用?】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;深度学习笔记 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Sequence-to-Sequence&#xff08;Seq2Seq&#xff09; Sequence-to-Sequence&#xff08;Seq2Seq…

加密函数f

1. 加密函数f 加密函数f(Ri-1,Ki)是DES中的核心算法,该函数包含选择运算E、异或运算、代替函数组S(S盒变换)、置换运算P,其流程如图所示。 (1)选择运算E与异或运算。选择运算E就是把Ri-1的32位扩展到48位,并与48位子秘钥Ki进行异或运算。具体扩展方式如图所示。 (2…

WordPres Bricks Builder 前台RCE漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

持续集成,持续交付和持续部署的概念,以及GitLab CI / CD的介绍

引言&#xff1a;上一期我们部署好了gitlab极狐网页版&#xff0c;今天我们介绍一下GitLabCI / CD 目录 一、为什么要 CI / CD 方法 1、持续集成 2、持续交付 3、持续部署 二、GitLab CI / CD简介 三、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 …

视界未来:Sora领航AI视频模型的科技进步

随着人工智能技术的飞速发展&#xff0c;AI视频模型正逐渐成为视频内容创作、编辑和呈现的重要工具。在这个充满潜力的领域中&#xff0c;Sora作为一种领先的AI视频模型&#xff0c;正引领着科技的进步&#xff0c;并为我们带来了无限的想象空间。本文将探讨Sora在科技进步中的…

SpringBoot+PDF.js实现按需分片加载预览(包含可运行示例源码)

SpringBootPDF.js实现按需分片加载预览 前言分片加载的效果前端项目前端项目结构前端核心代码前端项目运行 后端项目后端项目结构后端核心代码后端项目运行 项目运行效果首次访问分片加载 项目源码 前言 本文的解决方案旨在解决大体积PDF在线浏览加载缓慢、影响用户体验的难题…