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.rhkb.cn/news/368019.html

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

相关文章

mysql逗号分割字符串“1,2,3”实现in查询

数据示例 前台单值参数实现in查询 主要函数FIND_IN_SET 该函数的作用是查询字段(strlist) 中是否包含(str)的结果&#xff0c;返回结果为 null或记录 select id,recommend_position_id from t_stk_task where FIND_IN_SET(359919,recommend_position_id)查询效果 前台集…

vue3单个页面进行防抖节流

防抖 <template><button id"submitButton" ref"submitButton">GET</button> </template><script lang"ts" setup> import { ref, onMounted } from vue;// 防抖函数 function debounce(func: () > void, dela…

Python从入门到放弃——浮点型变量

浮点型变量 前言 上一篇文章我们研究了整数类型变量&#xff0c;本次我们来开始研究一下浮点类型变量。 浮点类型 浮点数在计算机编程中扮演着重要的角色。它们是一种特殊的数据类型&#xff0c;用于存储和处理小数或实数。在Python中&#xff0c;浮点数是由小数点分隔的…

[数据结构] --- 树

1 树的基本概念 1.1 树的定义 树是n(n>0)个结点的有限集。当 n 0 时&#xff0c;称为空树。在任意一棵树非空树中应满足&#xff1a; (1) 有且仅有一个特定的称为根 (root) 的结点&#xff1b; (2) 当 n > 1 时&#xff0c;其余结点可分为m(m>0)个互不相交的有限集…

【Unity 3D角色移动】

【Unity 3D角色移动】 在Unity 3D中实现角色移动通常涉及到几个关键步骤&#xff0c;包括设置角色的物理属性、处理输入、更新角色的位置以及动画同步。下面是实现基本3D角色移动的步骤和示例代码&#xff1a; 步骤1&#xff1a;设置角色的物理属性 角色通常使用Character Co…

单目相机减速带检测以及测距

单目相机减速带检测以及测距项目是一个计算机视觉领域的应用&#xff0c;旨在使用一个摄像头&#xff08;单目相机&#xff09;来识别道路上的减速带&#xff0c;并进一步估计车辆与减速带之间的距离。这样的系统对于智能驾驶辅助系统&#xff08;ADAS&#xff09;特别有用&…

【JavaWeb】利用IntelliJ IDEA 2024.1.4 +Tomcat10 搭建Java Web项目开发环境(图文超详细)

1、启动IntelliJ idea 2024.1.4 在欢迎页面&#xff0c;请确认好版本。因为不同的版本&#xff0c;搭建项目过程不太一样。 点击&#xff0c;新建项目。如图&#xff1a; 2、新建项目 在新建项目界面&#xff0c;选择java&#xff0c;在右侧信息模块内&#xff0c;根据个人情…

关于ant design vue 使用Modal无法关闭弹窗的解决思路

文章目录 1: 出现问题的版本2.出现问题&#xff08;1&#xff09;ant design 的问题&#xff08;2&#xff09;poina的提示报错 3.正确版本总结 1: 出现问题的版本 "ant-design-vue": "^3.2.20", "pinia": "^2.1.7", "vue"…

Ubuntu18.04新安装--无网络连接、重启黑屏解决教程

一、安装Ubuntu Ubuntu安装需要U盘作为启动盘&#xff0c;在目前教新的电脑中选中GPT作为分区&#xff0c;制作启动盘&#xff0c;其中在安装双系统Ubuntu时&#xff0c;以自定义格式作为存储空间。详细安装过程以以及如何分区请参考下列链接&#xff1a;内含详细安装过程&…

如何在Lazada平台快速出单?测评助力商家突破销量瓶颈

Lazada在短短的几年里已经发展成了东南亚地区最大的在线购物网站之一 &#xff0c;很多商家也想要在这样一个大的跨境平台上发展。那么&#xff0c;对于希望在Lazada平台上大展拳脚的商家而言&#xff0c;出单是否容易呢? ​一、Lazada出单容易吗? Lazada出单的难易程度并非…

Simulink 模型生成 C 代码(四):比较模型仿真和生成代码的结果

接下来将验证生成的代码执行时在数值上等效于 Simulink 中建模的算法。您使用测试框架模型在普通模式下对 RollAxisAutopilot 进行仿真&#xff0c;并在 SIL 模式下进行仿真&#xff0c;然后使用仿真数据检查器比较这两个仿真。 要测试生成的代码&#xff0c;您可以运行软件在…

Kubernetes基于helm安装 harbor

Kubernetes基于helm安装 harbor 之前harbor的安装都是借助docker完成一键安装部署&#xff0c;安装完成之后harbor组件均运行到一台机器上面&#xff0c;本文实践harbor在k8s环境中的部署。 准备工作 根据harbor官方要求&#xff1a; Kubernetes cluster 1.20Helm v3.2.0 …

SpringMVC基础详解

文章目录 一、SpringMVC简介1、什么是MVC2、MVC架构模式与三层模型的区别3、什么是SpringMVC 二、HelloWorld程序1、pom文件2、springmvc.xml3、配置web.xml文件4、html文件5、执行Controller 三、RequestMapping注解1、value属性1.1、基础使用1.2、Ant风格&#xff08;模糊匹配…

如何清理电脑内存?让电脑运行如飞!

电脑内存&#xff08;RAM&#xff09;的清理对于维持系统的流畅运行至关重要。随着使用时间的增加&#xff0c;系统内存会被各种应用程序和后台进程占用&#xff0c;导致系统响应变慢&#xff0c;甚至出现卡顿现象。通过有效地清理内存&#xff0c;可以提升电脑的性能&#xff…

数据库安全:MySQL权限体系划分与实战操作

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

网络基础:OSPF 协议

OSPF&#xff08;Open Shortest Path First&#xff09;是一种广泛使用的链路状态路由协议&#xff0c;用于IP网络中的内部网关协议&#xff08;IGP&#xff09;。OSPF通过在网络中的所有路由器之间交换路由信息&#xff0c;选择从源到目的地的最优路径。OSPF工作在OSI模型的第…

优化页面加载时间

注&#xff1a;机翻&#xff0c;未校对。 本文年代久远&#xff0c;除了少部分不合时宜的&#xff0c;其他仍有借鉴意义。 Optimizing Page Load Time 优化页面加载时间 It is widely accepted that fast-loading pages improve the user experience. In recent years, many …

【Elasticsearch】Elasticsearch动态映射与静态映射详解

文章目录 &#x1f4d1;前言一、Elasticsearch 映射概述1.1 什么是映射&#xff1f;1.2 映射的分类 二、动态映射2.1 动态映射的定义2.2 动态映射的优点2.3 动态映射的缺点2.4 动态映射的应用场景2.5 动态映射的配置示例 三、静态映射3.1 静态映射的定义3.2 静态映射的优点3.3 …

Zookeeper:Zookeeper集群角色

文章目录 一、Leader选举二、Zookeeper集群角色 一、Leader选举 Serverid&#xff1a;服务器ID&#xff1b;比如有三台服务器&#xff0c;编号越大在选择算法中的权重越大。Zxid&#xff1a;数据ID&#xff1b;服务器中存放的最大数据ID&#xff0c;值越大说明数据越新&#x…

JS(JavaScript) 数据校验 正则表达式

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…