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 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Fiddler工具 — 18.Fiddler抓包HTTPS请求(一)

1、Fiddler抓取HTTPS过程 第一步&#xff1a;Fiddler截获客户端发送给服务器的HTTPS请求&#xff0c;Fiddler伪装成客户端向服务器发送请求进行握手 。 第二步&#xff1a;服务器发回相应&#xff0c;Fiddler获取到服务器的CA证书&#xff0c; 用根证书&#xff08;这里的根证…

redis的搭建 RabbitMq搭建

官网 Download | Redis wget https://github.com/redis/redis/archive/7.2.4.tar.gz 编译安装 yum install gcc g tar -zxvf redis-7.2.4.tar.gz -C /usr/localcd /usr/local/redis make && make install 常见报错 zmalloc.h:50:10: fatal error: jemalloc/jemal…

创作无版权素材:解放创意的利器

title: 创作无版权素材&#xff1a;解放创意的利器 date: 2024/2/21 13:52:09 updated: 2024/2/21 13:52:09 tags: 无版权创作自由法律合规节省成本提升质量多样素材创意工具 在当今数字化时代&#xff0c;内容创作成为了一种非常重要的方式来传达信息和表达创意。 然而&#…

07 Redis之持久化(RDB+AOF)

4 Redis持久化 Redis 是一个内存数据库&#xff0c;然而内存中的数据是不持久的&#xff0c;若主机宕机或 Redis 关机重启&#xff0c;则内存中的数据全部丢失。 当然&#xff0c;这是不允许的。Redis 具有持久化功能&#xff0c;其会按照设置以快照或操作日志的形式将数据持…

电脑开机蓝屏错误代码c000021a怎么办 电脑蓝屏报错c000021a的解决办法

很多小伙伴在电脑开机的时候出现蓝屏代码c000021a都不知道该怎么去解决&#xff0c;所以今天就给你们带来了c000021a蓝屏解救方法&#xff0c;如果你还没解决的话就快来看看吧。 解决办法&#xff1a; 原因&#xff1a; c000021a蓝屏的原因有很多&#xff0c;主要有以下几种…

office 2021安装教程(官方自动批量激活,无付费)

全程不需要第三方软件&#xff0c;所有用到的工具都是微软官方的&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 基于KMS的 GVLK&#xff1a;https://learn.microsoft.com/zh-cn/deployoffice/vlactivation/gvlks 首先我们需要去下载 office 软件部署工具&a…

开发模式:分支开发主干发布

基于分支开发 该操作必须基于某稳定代码版本&#xff0c;来支持新的feature和bugfix、improvement。 分支命名规则 建议以版本号前三位命名分支&#xff08;1.0.1.12&#xff09; 备注填写分支简介&#xff0c;如&#xff1a;feature *** or ****** 关于提测准入及发布上线…

华清远见作业第三十九天——Qt(第一天)

思维导图&#xff1a; 登录界面&#xff1a; 代码&#xff1a; #include "mainwindow.h" #include<QToolBar> #include<QPushButton> MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {this->resize(600,400);this->setFixedSize…

PMP考试难度怎么样?

PMP考试整体难度并不大&#xff0c;因为目前的考试题型都是选择题&#xff0c;所以通过率相对较高。然而&#xff0c;新考纲中增加了更多关于敏捷方法的题型&#xff0c;这些题目相对更加变化多样&#xff0c;也有不少考生觉得难以抉择&#xff0c;四个选项都似乎都是正确的。P…

Python学习-列表1

十二、列表1 1、创建列表及基本运算 1&#xff09;使用中括号&#xff0c;将所有准备放入列表中的元素&#xff0c;包裹起来&#xff0c;不同元素之间使用逗号分隔。 举例&#xff1a; [1,2,3,4,5]2&#xff09;列表可以容纳不同类型的数据。 举例&#xff1a; [1,2,3,4,5,…

SmartForms笔记

目录 表Table表体区域脚标效果展示 带自增编号的结构新建循环设置变量的位置运行结果 表Table 右击“创建”—“表”&#xff1a; 绘制布局如下所示&#xff1a; 选择第一行&#xff0c;选择成“%LTYPE1”&#xff1a; 选择第二行&#xff0c;选择成“%LTYPE2”&#xff0c…

适用于高云FPGA的JTAG

目标板卡&#xff1a;小梅哥芯海无涯GOWIN高云ACG525(GW5A-LV25UG324) 1.软件要求&#xff1a;必须用商业版&#xff0c;因为教育版(V1.9.9Beta-4 Education)不支持此封装的GW5A。商业版需要上网申请License&#xff0c;此处提供D4D853392AD8.lic文件&#xff08;此方法为临时…

《MySQL MVCC 》

什么是 MySQL InnoDB 的 MVCC&#xff1f; MVCC (Multi-Version Concurrency Control)是一种基于多版本的并发控制协议&#xff0c;只有在 InnoDB 引擎下存在。MVCC 是为了实现事务的隔离性&#xff0c;即通过版本号&#xff0c;避免同一数据在不同事务间的竞争&#xff0c;可…

Job 和 DaemonSet

一、Job 1、Job 背景问题 K8s 里&#xff0c;最小的调度单元是 Pod&#xff0c;如果直接通过 Pod 来运行任务进程&#xff0c;会产生以下几种问题&#xff1a; ① 如何保证 Pod 内进程正确的结束&#xff1f; ② 如何保证进程运行失败后重试&#xff1f; ③ 如何管理多个任…

python在flask中的请求数据“无限流”

文章目录 一、问题描述二、解决方案 一、问题描述 在flask请求中&#xff0c;有个需求是让调用方一直调接口&#xff0c;并立马返回&#xff0c;而接口方缓存请求&#xff0c;依次执行。 二、解决方案 from flask import Flask, request, jsonify from queue import Queue i…

vue实现列表自动无缝滚动列表

大家好&#xff0c;今天给大家分享的知识是vue基于vue-seamless-scroll实现自动无缝滚动列表 一、实现自动滚动 最近在开发过程中遇到一个问题&#xff0c;就是需要实现自动滚动列表&#xff0c;效果图如下 就是这样一个列表在自动循环展示。在这里我是运用的 vue-seamless-sc…

数据结构——链表OJ题

目录 1.给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 2.给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点…

C#开发AGV地图编辑软件

C#自己开发AGV地图编辑软件&#xff1a; 1、自由添加和删除站点、停车位、小车、运行路径。 2、编辑得地图以XML文件保存。 3、导入编辑好地图的XML文件。 4、程序都是源码&#xff0c;可以直接在此基础上进行二次开发。 下载链接&#xff1a;https://download.csdn.net/d…

[极客挑战2019]HTTP

这道题考察的是http请求头字段的含义和使用&#xff1b; 具体如下 Referer:来源地址 User-Agent:客户端配置信息&#xff1a;浏览器类型、版本、系统类型等 X-Forwarded-For:代理地址&#xff0c;即数据发出的地址 开始解题&#xff1a;&#xff08;对我这初学者真的烧脑&a…

解决数学计算公式在前端项目里的展示,涉及换肤适配各个框架

有时候我们项目里面会嵌套一些数学公式说明 例如 可能你会发现市面上有很多的第三方库可以实现&#xff0c;比如&#xff1a; MathJax&#xff1a; https://www.mathjax.org/ 但是我们项目里面用到公式可能就一个页面&#xff0c;引一个第三方库进来会显得十分臃肿&#xff0…