Flutter第十五弹 Flutter插件

目标:

1.Flutter插件是什么?有什么作用?

插件 (plugin) 是 package 的一种,全称是 plugin package,我们简称为 plugin,中文叫插件。

2.怎么创建Flutter插件?

一、什么是插件

在flutter中,一个插件叫做一个package,使用packages的目的就是为了达到模块化,可以创建出可被复用和共享的代码,这和大多数编程语言中的模块、包的概念相同。创建出来的package可以在pubspec.yaml中直接依赖。

1.1 package组成

Flutter插件组成

  • 一个pubspec.yaml文件一个元数据文件,声明了声明了package的名称、版本、作者等信息。
  • 一个lib文件夹:包含里package的公开代码,文件夹至少需要存在<pakcage-name>.dart这个文件。

注意:<pakcage-name>.dart这个文件必须存在,因为这是方便使用的人快速import这个package来使用它,可以把它理解成一种必须要遵守的规则。

1.2 package分类

package可以分为两种:纯dart代码的package和带有特定平台代码的package。

  • Dart packages:这是一个只有dart代码的package,里面包含了flutter的特定功能,所以它依赖于flutter的framework,也决定了它只能用在flutter上。
  • plugin packages:这是一个既包含了dart代码编写的api,又包含了平台(Android/IOS)特定实现的package,可以被Android和ios调用。
  • FFI 插件
    用 Dart 语言编写针对一个或多个特定平台的 API,使用 Dart FFI (Android、iOS、macOS)。

> 上面应该很好理解,可以理解成java jar包和Android sdk的区别。而要开发的日志插件就是第二种。

二、插件开发

2.1 创建package

可以使用AS创建插件

然后点击next。

 

然后点击 Create按钮,开始创建插件项目。

如果是采用Flutter命令创建项目

// 想要创建初始的 Flutter package,请使用带有 --template=package 标志的 flutter create 命令:flutter create --template=package hello

2.2 项目文件结构

项目文件结构如下:

LICENSE 文件
大概率会是空的一个许可证文件。

  • test/hello_test.dart 文件

Package 的 单元测试 文件。

  • hello.iml 文件

由 IntelliJ 生成的配置文件。

  • .gitignore 文件

告诉 Git 系统应该隐藏哪些文件或文件夹的一个隐藏文件。

  • .metadata 文件

IDE 用来记录某个 Flutter 项目属性的的隐藏文件。

  • pubspec.yaml 文件

pub 工具需要使用的,包含 package 依赖的 yaml 格式的文件。

  • README.md 文件

起步文档,用于描述 package。

  • lib/hello.dart 文件

package 的 Dart 实现代码。

  • .idea/modules.xml.idea/workspace.xml 文件

IntelliJ 的各自配置文件(包含在 .idea 隐藏文件夹下)。

  • CHANGELOG.md 文件

又一个大概率为空的文档,用于记录 package 的版本变更。插件的native端实现

  • android/

插件包API的Android实现

  • iOS

插件包API的ios实现.

  • example/:

   一个依赖于该插件的Flutter应用程序,来说明如何使用它

lib库定义插件的主要功能。

2.3 实现插件

对于纯 Dart 库的 package,只要在 lib/<package name>.dart 文件中添加功能实现,或在 lib 目录中的多个文件中添加功能实现。
如果要对 package 进行测试,在 test 目录下添加 单元测试。

2.3.1 创建MethodChannel

项目默认生成了插件MethodChannel

1. 创建MethodChannel

flutter_log_plugin_method_channel.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';import 'flutter_log_plugin_platform_interface.dart';/// An implementation of [FlutterLogPluginPlatform] that uses method channels.
class MethodChannelFlutterLogPlugin extends FlutterLogPluginPlatform {/// The method channel used to interact with the native platform.@visibleForTestingfinal methodChannel = const MethodChannel('flutter_log_plugin');@overrideFuture<String?> getPlatformVersion() async {final version = await methodChannel.invokeMethod<String>('getPlatformVersion');return version;}
}
2.定义插件方法 
  • 创建了一个MethodChannel,名称为flutter_log_plugin
  • 提供了一个方法访问getPlatformVersion

我们看看其调用的方式,通过创建的methodChannel.invokeMethod来调用原生实现。

final version = await methodChannel.invokeMethod<String>('getPlatformVersion');

2.3.2 插件方法Native实现

在项目 android 目录下,增加对 MethodChannel 方法的实现。

默认的插件实现的功能:Dart通过插件,获取 native端系统版本信息。

在android/src.main  下,实现了Native方法

package com.example.flutter_log_pluginimport androidx.annotation.NonNullimport io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result/** FlutterLogPlugin */
class FlutterLogPlugin: FlutterPlugin, MethodCallHandler {/// The MethodChannel that will the communication between Flutter and native Android////// This local reference serves to register the plugin with the Flutter Engine and unregister it/// when the Flutter Engine is detached from the Activityprivate lateinit var channel : MethodChanneloverride fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_log_plugin")channel.setMethodCallHandler(this)}/*** 方法调用处理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {result.success("Android ${android.os.Build.VERSION.RELEASE}")} else {result.notImplemented()}}override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}
}
MethodChannel提供Flutter与原生系统之间的通信。
1.绑定MethodChannel: Activity attach到Flutter引擎
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it/// when the Flutter Engine is detached from the Activityprivate lateinit var channel : MethodChanneloverride fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_log_plugin")channel.setMethodCallHandler(this)}

因为Flutter提供的引擎是“flutter_log_plugin”名称,因此通过 命名找到对应的MethodChannel。

这样Activity就可以绑定Flutter MethodChannel,可以建立通信通道。

设置MethodCallHandler,注册插件到Flutter引擎。

channel.setMethodCallHandler(this)
2. Flutter调用Native方法

建立通道以后,Flutter调用Native端方法,方法名为getPlatformVersion,没有参数。

  /*** 方法调用处理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {result.success("Android ${android.os.Build.VERSION.RELEASE}")} else {result.notImplemented()}}

Native端接收方法调用的入口是 onMethodCall

  • 首先匹配方法名
  • 根据call的参数进行处理
  • 返回方法调用结果,通过result保存结果值。
  • 如果对应名称的方法未实现,则设置 result.notImplented()

当前获取安卓系统版本,返回结果是

"Android ${android.os.Build.VERSION.RELEASE}"
3.Activity与Flutter引擎断开时注销插件
  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}

 2.3.3 实现日志打印插件

1.声明接口方法

在lib插件的接口文件flutter_log_plugin_platform_interface.dart

声明接口方法 logI

  /*** 声明接口方法** @author zhouronghua* @time 2024/6/27 下午4:05*/void logI(String tag, String message) {throw UnimplementedError('logI() has not been implemented.');}
2. 插件端定义日志打印方法实现 logI
  /*** 日志打印:I级别* 说明: 日志打印不需要接收结果,因此不需要异步回调** @author zhouronghua* @time 2024/6/27 下午3:45*/@overridevoid logI(String tag, String message) {/// 调用原生方法logI, 参数集为 {tag, message}/// 参数集按照键值对传递methodChannel.invokeMethod('logI', {"tag": tag, "message": message});}

调用的Native段方法名为 “logI”,对应的参数为:

{"tag": tag, "message": message}

参数一般使用键值对进行传递,参数之间采用逗号分隔。

注意,此处一定要使用注解 @override,否则调用logI编译报错

3.Native端实现方法接收处理

在android/src.main下,FlutterLogPlugin增加日志方法调用的实现。

/*** 方法调用处理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {// 获取系统版本信息result.success("Android ${android.os.Build.VERSION.RELEASE}")} else if (call.method == "logI") {// 日志打印处理 logI(参数要与插件的键保持一致)final String tag = call.argument("tag")final String message = call.argument("message")android.util.Log.i(tag, message)} else {result.notImplemented()}}
4.Flutter插件入口添加日志打印方法

FlutterLogPlugin中,增加日志打印入口调用

/*** Flutter插件入口* 门面模式** @author zhouronghua* @time 2024/6/27 下午4:11*/
class FlutterLogPlugin {Future<String?> getPlatformVersion() {return FlutterLogPluginPlatform.instance.getPlatformVersion();}/*** 日志打印调用** @author zhouronghua* @time 2024/6/27 下午4:11*/void logI(String tag, String message) {return FlutterLogPluginPlatform.instance.logI(tag, message);}}

这个是典型的门面模式,外部调用的使用不需要关注Flutter插件内部实现细节。

5.测试日志打印方法

在example/lib下,main.dart中,测试日志打印方法调用。

// Platform messages are asynchronous, so we initialize in an async method.Future<void> initPlatformState() async {// 调用日志打印_flutterLogPlugin.logI("MyApp", "开始初始化平台");String platformVersion;// Platform messages may fail, so we use a try/catch PlatformException.// We also handle the message potentially returning null.try {platformVersion = await _flutterLogPlugin.getPlatformVersion() ??'Unknown platform version';} on PlatformException {platformVersion = 'Failed to get platform version.';}// 调用日志打印_flutterLogPlugin.logI("MyApp", "平台信息是:$platformVersion");// If the widget was removed from the tree while the asynchronous platform// message was in flight, we want to discard the reply rather than calling// setState to update our non-existent appearance.if (!mounted) return;setState(() {_platformVersion = platformVersion;});}

问题一:编译报错logI未实现

还是报错

Restarted application in 1,896ms.
E/flutter ( 2023): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: MissingPluginException(No implementation found for method logI on channel flutter_log_plugin)
E/flutter ( 2023): #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
E/flutter ( 2023): <asynchronous suspension>
E/flutter ( 2023): 
E/flutter ( 2023): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: MissingPluginException(No implementation found for method logI on channel flutter_log_plugin)
E/flutter ( 2023): #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
E/flutter ( 2023): <asynchronous suspension>
E/flutter ( 2023): 

test模块,flutter_log_plugin_test.dart缺少对应的logI方法的实现,因此报错。添加

class MockFlutterLogPluginPlatform with MockPlatformInterfaceMixinimplements FlutterLogPluginPlatform {@overrideFuture<String?> getPlatformVersion() => Future.value('42');@overridevoid logI(String tag, String message) {debugPrint("tag=$tag message=$message");}
}
6.打包apk

Terminal中,编译安卓APK。

$ cd example/android$ gradlew clean$ flutter build apk

如果报错,修正报错信息,重新打包试试。

出现下面的信息则编译成功。

安装运行APK,看看是否打印日志信息

能够输出对应的日志信息。

三、插件原理

Plugin其实就是一个特殊的Package。Flutter Plugin提供Android或者iOS的底层封装,在Flutter层提供组件功能,使Flutter可以较
方便的调取Native的模块。很多平台相关性或者对于Flutter实现起来比较复杂的部分,都可以封装成Plugin。

3.1 Platform Channel

Platform Channel:

1. Flutter App (Client),通过MethodChannel类向Platform发送调用消息;
2. Android Platform (Host),通过MethodChannel类接收调用消息;
3. iOS Platform (Host),通过FlutterMethodChannel类接收调用消息。

  • > PS:消息编解码器,是JSON格式的二进制序列化,所以调用方法的参数类型必须是可JSON序列化的。
  • > PS:方法调用,也可以反向发送调用消息。

3.2 安卓平台

FlutterActivity,是Android的Plugin管理器,它记录了所有的Plugin,并将Plugin绑定到FlutterView。 

3.3 理解Platform Channel工作原理


Flutter定义了三种不同类型的Channel,它们分别是

  • BasicMessageChannel:用于传递字符串和半结构化的信息。
  • MethodChannel:用于传递方法调用(method invocation)。
  • EventChannel: 用于数据流(event streams)的通信。

三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量:

  • name:  String类型,代表Channel的名字,也是其唯一标识符。
  • messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
  • codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。
  • Channel name

​   一个Flutter应用中可能存在多个Channel,每个Channel在创建时必须指定一个独一无二的name,Channel之间使用name来区分彼此。当有消息从Flutter端发送到Platform端时,会根据其传递过来的channel name找到该Channel对应的Handler(消息处理器)。

  • 消息信使:BinaryMessenger
  • 平台通道数据类型支持和解码器
  • 标准平台通道使用标准消息编解码器,以支持简单的类似JSON值的高效二进制序列化,例如 booleans,numbers, Strings, byte buffers, List, Maps(请参阅StandardMessageCodec了解详细信息)。 当您发送和接收值时,这些值在消息中的序列化和反序列化会自动进行。

下表显示了如何在宿主上接收Dart值,反之亦然:

 3.4 解码器

 

消息解码器主要将二进制格式的数据转换为Handler能够识别的数据,Flutter定义了两种Codec:

MessageCodec和MethodCodec。

四、插件打包和发布

4.1 插件检查

一旦完成了 package 的实现,你便可以将其提交到 pub.dev 上,以便其他开发者可以轻松地使用它。

发布你的 package 之前,确保检查了这几个文件:pubspec.yamlREADME.md 和 CHANGELOG.md,确保它们完整且正确,另外,为了提高 package 的可用性,可以考虑加入如下的内容:

  • 代码的示例用法

  • 屏幕截图,GIF 动画或者视频

  • 代码库的正确指向链接

运行 dry-run 命令以检验是否所有内容都通过了分析:

$ flutter packages pub publish --dry-run

修正提示错误信息。 

pubspec.yaml 中anthor字段不需要了,直接删除

修改后再次执行。 

Package validation found the following potential issue:
* Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version. If this package needs Dart version 2.17.0-239.0.dev, consider publishing the package as a pre-release instead.See https://dart.dev/tools/pub/publishing#publishing-prereleases For more information on pre-releases.Package has 1 warning.
pub finished with exit code 65

 

4.2 插件发布

最后一步是发布,请注意:发布是永久性 的,运行以下提交命令:

flutter pub publish

设置了中国镜像的开发者们请注意:目前所存在的镜像都不能(也不应该)进行 package 的上传。如果你设置了镜像,执行上述发布代码可能会造成发布失败。网络设定好后,无需取消中文镜像,执行下述代码可直接上传:

flutter pub publish --server=https://pub.dartlang.org

 

Dart 概览 | Dart

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【成都活动邀请函】7月6 | PowerData 数字经济-“成都“开源行!

【成都活动邀请函】7月6 | PowerData 数字经济-"成都"开源行&#xff01; 活动介绍活动信息线上直播扫码报名往期活动回顾专注数据开源&#xff0c;推动大数据发展 活动介绍 九天开出一成都&#xff0c;万户千门入画图。 自古以来&#xff0c;成都便是国家发展的重要…

第2章-Python编程基础

#本章目标 1&#xff0c;了解什么是计算机程序 2&#xff0c;了解什么是编程语言 3&#xff0c;了解编程语言的分类 4&#xff0c;了解静态语言与脚本语言的区别 5&#xff0c;掌握IPO程序编写方法 6&#xff0c;熟练应用输出函数print与输入函数input 7&#xff0c;掌握Python…

【机器学习】机器学习的重要技术——生成对抗网络:理论、算法与实践

引言 生成对抗网络&#xff08;Generative Adversarial Networks, GANs&#xff09;由Ian Goodfellow等人在2014年提出&#xff0c;通过生成器和判别器两个神经网络的对抗训练&#xff0c;成功实现了高质量数据的生成。GANs在图像生成、数据增强、风格迁移等领域取得了显著成果…

leetCode.97. 交错字符串

leetCode.97. 交错字符串 题目思路 代码 class Solution { public:bool isInterleave(string s1, string s2, string s3) {int n s1.size(), m s2.size();if ( s3.size() ! n m ) return false;vector<vector<bool>> f( n 1, vector<bool> (m 1));s1 …

Sparse4D v3: Advancing End-to-End 3D Detection and Tracking

Sparse4D v3: Advancing End-to-End 3D Detection and Tracking 相关内容&#xff1a;总览&#xff0c;Sparse4D v1&#xff0c;Sparse4D v2&#xff0c; 单位&#xff1a;地平线(Sparse4D v1 v2 原班人马) GitHub&#xff1a;https://github.com/HorizonRobotics/Sparse4D …

昇思25天学习打卡营第5天 | 网络构建

目录 1.定义模型类 2.模型层 nn.Flatten nn.Dense nn.ReLU nn.SequentialCell nn.Softmax 3.模型参数 代码实现&#xff1a; 总结 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c; mindspore.nn提供了常见神经网络层的实现&#xff0c; 在MindSpore中&a…

AI智能客服项目拆解(1) 产品大纲

本文作为拆解AI智能客服项目的首篇&#xff0c;以介绍产品大纲为主。后续以某AI智能客服产品为例&#xff0c;拆解相关技术细节。 AI智能客服是一种基于人工智能技术的客户服务解决方案&#xff0c;旨在提高客户满意度和优化企业运营。利用人工智能和自然语言处理技术&#xff…

MySQL之索引失效的情况

什么情况下索引会失效&#xff1f; 违反最左前缀原则范围查询右边的列不能使用索引不要在索引列上进行运算操作字符串不加单引号导致索引失效以%开头的like模糊查询 什么情况下索引会失效&#xff1f; 示例&#xff0c;有user表如下 CREATE TABLE user (id bigint(20) NOT NU…

JAVA期末速成库(11)第十二章

一、习题介绍 第十二章 Check Point&#xff1a;P454 12.1&#xff0c;12.9&#xff0c;12.10&#xff0c;12,12 二、习题及答案 12.1 What is the advantage of using exception handling? 12.1使用异常处理的优势是什么? 答:使用异常处理有以下优势&#xff1a; 1. 提高…

Spark join数据倾斜调优

Spark中常见的两种数据倾斜现象如下 stage部分task执行特别慢 一般情况下是某个task处理的数据量远大于其他task处理的数据量&#xff0c;当然也不排除是程序代码没有冗余&#xff0c;异常数据导致程序运行异常。 作业重试多次某几个task总会失败 常见的退出码143、53、137…

【电路笔记】-放大器类型

放大器类型 文章目录 放大器类型1、概述2、关于偏置的注意事项3、A类(Class A)放大器4、B类(Class B)放大器5、AB类(Class AB)放大器6、C类(Class C)放大器7、总结1、概述 放大器通常根据输出级的结构进行分类。 事实上,功率放大确实发生在该阶段,因此输出信号的质量和…

Java SE入门及基础(59) 线程的实现(上) 线程的创建方式 线程内存模型 线程安全

目录 线程&#xff08;上&#xff09; 1. 线程的创建方式 Thread类常用构造方法 Thread类常用成员方法 Thread类常用静态方法 示例 总结 2. 线程内存模型 3.线程安全 案例 代码实现 执行结果 线程&#xff08;上&#xff09; 1. 线程的创建方式 An application t…

利用 Docker 简化 Nacos 部署:快速搭建 Nacos 服务

利用 Docker 简化 Nacos 部署&#xff1a;快速搭建 Nacos 服务 引言 在微服务架构中&#xff0c;服务注册与发现是确保服务间通信顺畅的关键组件。Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;作为阿里巴巴开源的一个服务发现和配置管理平台&…

任务调度器——任务切换

一、开启任务调度器 函数原型&#xff1a; void vTaskStartScheduler( void ) 作用&#xff1a;用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c; FreeRTOS 便会开始进行任务调度 内部实现机制&#xff08;以动态创建为例&#xff09;&#xff1a; &#xff0…

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 下载页 ->Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人…

测评推荐:企业管理u盘的软件有哪些?

U盘作为一种便携的存储设备&#xff0c;方便易用&#xff0c;被广泛应用于企业办公、个人学习及日常工作中。然而&#xff0c;U盘的使用也带来了数据泄露、病毒传播等安全隐患。为了解决这些问题&#xff0c;企业管理U盘的软件应运而生。 本文将对市面上流行的几款U盘管理软件…

Hadoop3:Yarn容量调度器配置多队列案例

一、情景描述 需求1&#xff1a; default队列占总内存的40%&#xff0c;最大资源容量占总资源60%&#xff0c;hive队列占总内存的60%&#xff0c;最大资源容量占总资源80%。 二、多队列优点 &#xff08;1&#xff09;因为担心员工不小心&#xff0c;写递归死循环代码&#…

电路笔记(电源模块): 基于FT2232HL实现的jtag下载器硬件+jtag的通信引脚说明

JTAG接口说明 JTAG 接口根据需求可以选择20针或14针的配置&#xff0c;具体选择取决于应用场景和需要连接的功能。比如之前的可编程逻辑器件XC9572XL使用JTAG引脚&#xff08;TCK、TDI、TDO、TMS、VREF、GND&#xff09;用于与器件进行调试和编程通信。更详细的内容可以阅读11…

51单片机STC8H8K64U通过RA8889/RA8876如何控制彩屏(SPI源码下载)

【硬件部份】 一、硬件连接实物&#xff1a; STC8H系列单片机不需要外部晶振和外部复位&#xff0c;在相同的工作频率下&#xff0c;速度比传统的8051单片机要快12倍&#xff0c;具有高可靠抗干扰的优秀特性&#xff0c;与瑞佑的RA8889/RA8876控制芯片刚好可以完美搭配用于工…

redis实战-缓存雪崩问题及解决方案

定义理解 缓存雪崩是指在同一时间段&#xff0c;大量缓存的key同时失效&#xff0c;或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力 和缓存击穿的区别&#xff1a; 缓存雪崩是由于缓存中的大量数据同时失效或缓存服务器故障引起的&#xff1b…