Flutter第十五弹 Flutter插件

news/2024/7/7 22:13:08 标签: 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.
  @visibleForTesting
  final methodChannel = const MethodChannel('flutter_log_plugin');

  @override
  Future<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_plugin

import androidx.annotation.NonNull

import 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 Activity
  private lateinit var channel : MethodChannel

  override 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 Activity
  private lateinit var channel : MethodChannel

  override 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
   */
  @override
  void 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 MockPlatformInterfaceMixin
    implements FlutterLogPluginPlatform {

  @override
  Future<String?> getPlatformVersion() => Future.value('42');

  @override
  void 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.niftyadmin.cn/n/5535403.html

相关文章

51-5 权限维持2 - 影子账号(隐藏用户)

权限维持技术 权限维持技术(Persistence,也称为权限持久化)是一种能够在系统重启、用户更改密码或其他可能导致访问中断的情况下保持对系统访问的技术。例如,它包括创建系统服务、利用计划任务、修改系统启动项或注册表、以及映像劫持等方法。 创建影子账户 影子账户是指隐…

【数据同步】什么是ETL增量抽取?

目录 一、什么是ETL增量抽取 二、企业如何应用ETL增量抽取 三、如何进行ETL增量抽取 1.基于时间戳的增量抽取 2.基于主键的增量抽取 在当今信息化时代&#xff0c;数据的快速增长和多样化使得企业面临着巨大的数据管理挑战。为了高效地处理和利用数据&#xff0c;ETL&#xff0…

ONLYOFFICE8.1版本桌面编辑器的测评(您的私人办公室)

ONLYOFFICE官网链接&#xff1a;ONLYOFFICE - 企业在线办公应用软件 | ONLYOFFICE 在线PDF查看器和转换器 | ONLYOFFICE​​​​​​在线办公套件 | ONLYOFFICE 一&#xff0c;引言 在数字化浪潮中&#xff0c;高效、便捷、安全的办公工具对现代职场至关重要。今天&#xff0c;…

全面解析自然语言处理(NLP):基础、挑战及应用前景

自然语言处理 (NLP) 简介与应用前景 自然语言处理&#xff08;NLP&#xff09;是人工智能和计算语言学的一个分支&#xff0c;致力于使计算机能够理解、解释和生成人类语言。这篇博文将深入探讨自然语言处理的基础知识、挑战、典型任务及其广泛的应用前景。 一、自然语言处理的…

7.4总结

今天写了几道题目 最近&#xff0c;一年级学生马克西姆学习了科拉兹猜想&#xff0c;但他在讲课时没有太注意&#xff0c;所以他认为猜想中提到了以下过程&#xff1a; 有一个变量 $$$x$$$ 和一个常数 $$$y$$$ 。下面的操作要执行 $$$k$$$ 次&#xff1a; - 将 $$$x$$$ 增加…

JavaSE (Java基础):面向对象(下)

8.7 多态 什么是多态&#xff1f; 即同一方法可以根据发送对象的不同而采用多种不同的方式。 一个对象的实际类型是确定的&#xff0c;但可以指向对象的引用的类型有很多。在句话我是这样理解的&#xff1a; 在实例中使用方法都是根据他最开始将类实例化最左边的类型来定的&…

【Qt知识】window frame 对窗口坐标的影响

在Qt中&#xff0c;窗口框架&#xff08;Window Frame&#xff09;对Widget的尺寸计算和坐标定位有着直接的影响&#xff0c;这主要是因为窗口框架本身占据了一定的空间&#xff0c;包括标题栏、最小化/最大化/关闭按钮以及边框。这部分额外的空间在不同的应用场景下需要被考虑…

C++视觉开发 一.OpenCV环境配置

一.OpenCV安装环境配置 1.OpenCV安装 &#xff08;1&#xff09;下载 官方下载链接&#xff1a;http://opencv.org/releases 这边选择需要的版本&#xff0c;我是在windows下的4.9.0。&#xff08;科学上网下载很快&#xff0c;否则可能会有点慢&#xff09; (2)安装 双击下…