Flutter 插件

1、Flutter插件是什么?官方插件库

在开发Flutter应用过程中会涉及到平台相关接口调用,例如数据库操作、相机调用、外部浏览器跳转等业务场景。其实Flutter自身并不支持直接在平台上实现这些功能,而是通过插件包接口去调用指定平台API从而实现原生平台上特定功能。

2、Flutter插件的目录结构

  • lib 是对接dart端代码的入口,由此文件接收到参数后,通过chennel将数据发送到原生端
  • android 安卓端代码实现目录
  • ios ios原生端实现目录
  • example 一个依赖于该插件的Flutter应用程序,来说明如何使用它
  • README.md:介绍包的文件
  • CHANGELOG.md 记录每个版本中的更改
  • LICENSE 包含软件包许可条款的文件

3、Flutter插件包的创建方式

  • 使用命令行创建
flutter create --template=package hello

可以通过–org指定包标识符

flutter create --template=package hello

通过参数指定ios和Android代码使用的语言类型

flutter create --template=plugin -i swift -a kotlin hello
  • 使用AS直接new工程

4、Flutter插件功能编写

flutter 插件模板生成后,在lib文件夹下会自动生成一个对外的入口dart类,该插件所包含的所有功能都以此类为入口,来提供外部进行调用。以一个名字为hello的插件为例

platformVersion 是对外的方法调用,但是方法内部的实现逻辑,是通过原生端去获取的。对应android原生端的入口文件如下

监听来自dart端的请求,需要继承MethodChannel.MethodCallHandler接口,然后在onMethodCall方法回调中处理和返回给dart端数据逻辑。
result是给dart端回传最后结果的,如果dart不需要返回结果,也可以不调用

result.success(Object o)

如果一些简单的需求,可以直接在此处的plugin里实现,最后将结果直接返回。但是比如调起相机拍照,选取通讯录联系人,这些都要打开一个intent然后在OnActivityResuult方法中去获取最终的结果,这种情况下如何处理呢?

继承 PluginRegistry.ActivityResultListener 接口

注意!!! > 直接将源码放在项目中的插件,在运行时候onActivityResult方法是不会被调用的,因为MainActivity中的onActivityResult将调用动作拦截了下来,所以必须将插件放在远端仓库中才可以正常接收

implements PluginRegistry.ActivityResultListener {........@Overridepublic boolean onActivityResult(int requestCode, int resultCode, Intent data) {// 在此处写逻辑,最后拿到结果后通过callback回调到onMethodCall方法内,再回传给dartreturn false;}}

5、Flutter插件的两种注册方式

①通过 registerWith 方式注册,早期非常老旧的方式

registerWith方式是通过反射进行加载

目前我们项目里的插件都是使用这种方式注册,但是从flutter v1.12.x 开始往后官方推荐使用第二种方式注册,第一种方式会在以后的更新中废除,所以以后更新flutter大版本,可能要重新修改现有插件的注册方式

//此处是旧的插件加载注册方式,静态方法public static void registerWith(Registrar registrar) {final MethodChannel channel = new MethodChannel(registrar.messenger(), PLUGIN_NAME);channel.setMethodCallHandler(new FlutterXUpdatePlugin().initPlugin(channel, registrar));}

如果是旧的方式注册的插件,获取activity对象时候使用

registrar.activity()

通过Flutter引擎注册

在Flutter1.12.X 版本中正式将Embedding-V2API在Android平台默认开启,所有官方插件都迁移到了新的API。Embedding-V2APi的优势在于针对混合开发提供了更好的支持和内存上的优化

插件的注册方式定义在工程的android端的mainfest.xml文件中,如下所示:

//新的注册方式必须指定,旧的方式无需指定此配置
<meta-dataandroid:name="flutterEmbedding"android:value="2" />

在插件的plugin文件中,继承FlutterPlugin接口,使用以下新的方式进行初始化


//此处是新的插件加载注册方式@Overridepublic void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {mMethodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), PLUGIN_NAME);mApplication = (Application) flutterPluginBinding.getApplicationContext();mMethodChannel.setMethodCallHandler(this);}@Overridepublic void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {mMethodChannel.setMethodCallHandler(null);mMethodChannel = null;}

如需获取当前插件依附的activity,也就是mainActivity,则需要plugin集成ActivityAware接口,然后通过回调获取

 @Overridepublic void onAttachedToActivity(ActivityPluginBinding binding) {mActivity = new WeakReference<>(binding.getActivity());}@Overridepublic void onDetachedFromActivity() {mActivity = null;}

6、Flutter 与原生之间如何交互

Flutter与原生的交互模型,类似于一种C-S模型。其中Flutter为Client层,原生为Server层,两者通过MethodChannel进行消息通信,原生端向Flutter提供已有的Native组件功能。

在客户端,MethodChannel允许发送与方法调用相对应的消息。 在平台方面,Android上的MethodChannel和iOS上的FlutterMethodChannel启用接收方法调用并返回结果。 这些类允许你使用非常少的“样板”代码开发平台插件。

Flutter与原生的消息传递采用标准信息编解码器,是一种相对高效的二进制序列化与反序列化。当接收跟发送消息时,这些值在消息中会自动进行序列化与反序列化。详细的请参阅StandardMessageCodec

(1) 什么是MethodChannel?

Flutter定义了3中channel模型:

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

MethodChannel总共有3个成员变量

String name

在Flutter中会存在多个Channel,一个Channel对象通过name来进行唯一的标识,所以在Channel的命名上一定要独一无二,推荐采用组件名_Channel名 组合来进行命名

BinaryMessenger messenger

  • BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。
  • Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。
  • Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。
  • 当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger返回。

MethodCodec codec

  • 消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据
  • MethodCodec主要是对MethodCall中这个对象进行序列化与反序列化
  • MethodCall是Flutter向Native发起调用产生的对象,其中包含了方法名以及一个参数集合(map或者是Json)

(2) Flutter 与原生之间的通信流程

首先从dart层调用

_channel.invokeMethod("方法名",参数)
  • invoke方法会将传入的方法名与参数封装成MethodCall对象
  • 然后通过MethodCodec对MethodCall对象进行编码,形成二进制格式。
  • 然后通过BinaryMessenger的send方法,将二进制格式的数据进行发送
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {  assert(method != null);///发送 messengefinal dynamic result = await BinaryMessages.send(name,codec.encodeMethodCall(MethodCall(method, arguments)),);if (result == null)throw MissingPluginException('No implementation found for method $method on channel $name');return codec.decodeEnvelope(result);}

send方法里,dart层最终调用native方法 Window_sendPlatformMessage ,将序列化后的MethodCall对象向 C 层发送

static Future<ByteData> send(String channel, ByteData message) {  final _MessageHandler handler = _mockHandlers[channel];if (handler != null) return handler(message);return _sendPlatformMessage(channel, message);
}String _sendPlatformMessage(String name,  PlatformMessageResponseCallback callback,ByteData data) native 'Window_sendPlatformMessage';

我们在Flutter engine的native代码中可以找到上述native方法的对应实现,这里截取关键部分,可以看到最后是交给了WindowClient的handlePlatformMessage方法进行实现

dart_state->window()->client()->HandlePlatformMessage(  fml::MakeRefCounted<PlatformMessage>(name, response));

(这里以Android举例,iOS同理)可以看到,在Android平台HandlePlatformMessage方法中,调用到了JNI方法,将c层收到的信息向java层抛

void PlatformViewAndroid::HandlePlatformMessage(  fml::RefPtr<blink::PlatformMessage> message) {JNIEnv* env = fml::jni::AttachCurrentThread();fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);auto java_channel = fml::jni::StringToJavaString(env, message->channel()); if (message->hasData()) {fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(env, env->NewByteArray(message->data().size()));env->SetByteArrayRegion(message_array.obj(), 0, message->data().size(),reinterpret_cast<const jbyte*>(message->data().data()));message = nullptr;// This call can re-enter in InvokePlatformMessageXxxResponseCallback.FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),message_array.obj(), response_id);  } else {message = nullptr;// This call can re-enter in InvokePlatformMessageXxxResponseCallback.FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),nullptr, response_id);           }
}

看一下JNI对应的java方法,最终通过handler.onMessage(),完成了本次dart信息的传递。方法中的handler,就是我们前面提到的MethodHandler,也是我们插件的Native模块注册的MethodHandler,每一个MethodHandler 都和 MethodChannel是一一对应的关系

private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {this.assertAttached();BinaryMessageHandler handler = (BinaryMessageHandler)this.mMessageHandlers.get(channel); if (handler != null) {try {ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);handler.onMessage(buffer, new BinaryReply() {// ...});} catch (Exception var6) {// ...}} else {Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);nativeInvokePlatformMessageEmptyResponseCallback(this.mNativePlatformView, replyId);}}

此处的handler.onMessage方法内调用了plugin集成的MethodCallHandler接口的 onMethodCall 方法:

同时在onMethodCall方法中会传入第二个参数 Result ,当处理完拿到dart想要的结果数据后,通过Result来进行回传。

public interface Result {  void success(@Nullable Object var1);void error(String var1, @Nullable String var2, @Nullable Object var3);void notImplemented();}

(3)MethodChannel是什么时候注册,和MethodHandler联系起来的呢?

在插件运行的时候,我们会调用插件的registerWith方法,在生成MethodChannel对象时,同时向MethodChannel注册了一个MethodHandler,MethodHandler对象跟MethodChannel对象是一一对应的。

7、原生和Flutter之间数据交互的类型限制

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

8、插件包的发布

发布过程参考Flutter中文网Package发布教程

Flutter 自定义插件基础相关推荐

  1. JQuery自定义插件详解之Banner图滚动插件

      前  言 JRedu JQuery是什么相信已经不需要详细介绍了.作为时下最火的JS库之一,JQuery将其"Write Less,Do More!"的口号发挥的极致.而帮助J ...

  2. flutter常用插件

    学习资料 https://marcinszalek.pl/ 简书1 :https://www.jianshu.com/p/9e5cc4ba3a8e 掘金布局:https://juejin.im/pos ...

  3. 【Flutter】Flutter 自定义字体 ( 下载 TTF 字体 | pubspec.yaml 配置字体资源 | 同步资源 | 全局应用字体 | 局部应用字体 )

    文章目录 一.Flutter 自定义字体 1.ttf 字体文件 2.ttf 字体资源配置 3.获取字体 4.全局使用字体 5.局部使用字体 二.完整代码示例 三.相关资源 一.Flutter 自定义字 ...

  4. 技术干货 | Flutter 混合开发基础

    导读:Flutter 支持以独立页面.甚至是 UI 片段的方式,集成到现有的应用中,即所谓的混合开发模式.本文主要谈谈 Android 平台下, Flutter 的混合开发与构建. 文|李成达 网易云 ...

  5. 最佳阵容 | Flutter Firebase 插件更新

    作者 / Chris Sells, Flutter 开发者体验产品经理 Flutter 不仅是一个引擎.一套 widget 和一些工具,它还包括一个庞大的 package 生态系统,让应用得以实现远多 ...

  6. 自定义插件解决MyBatis-Plus like查询遇_ % \等字符需转译问题(含分页查询)

    我们使用MyBatis-Plus执行LIKE模糊查询时,若预处理参数包含_ % \等字符(欢迎补充),会查询出所有结果,这不是我们需要的. 不论写法是自定义SQL xxx like concat('% ...

  7. Flutter 自定义动画 — 数字递增动画和文字逐行逐字出现或消失动画

    系列文章 Flutter 旋转动画 - RotationTransition Flutter 平移动画 - 4种实现方式 Flutter 淡入淡出与逐渐出现动画 Flutter 尺寸缩放.形状.颜色. ...

  8. 超详细手把手教你cordova开发使用指南+自定义插件,jsbridge

    Cordova是什么 使用前端技术 开发跨平台web App的工具 底层原理:HTML+CSS搭建页面, JS和原生交互 交互原理:Cordova插件 环境配置 安卓开发基础环境搭建的文章可以参考一下 ...

  9. java自定义maven插件_Maven自定义插件的实现

    为了快速学习自定义 Maven 插件的过程,接下来将实现一个简单的 Hello Maven 插件,功能很简单:输出 Hello World 插件.具体步骤和操作如下. 创建 Maven 新项目,选择 ...

最新文章

  1. iterm2 mac链接linux工具 桌面程序Transmit
  2. Hello OpenGL——OpenGL在Visual c++6.0安装和配置
  3. VB模拟指针模块mPoint.bas
  4. Linux系统管理第七周作业【Linux微职位】
  5. 树莓派指定python2编译_在树莓派上编译安装ROS2
  6. Java经典编程题50道之四十二
  7. 电脑预览,电脑怎么预览psd格式?
  8. c语言pi算法程序,C语言计算圆周率PI
  9. 2008中国IT前瞻
  10. JAVA中的getBytes方法
  11. 【循序渐进学运维】MySQL运维系列文章汇总
  12. 说说你对 SPA 单页面的理解,它的优缺点分别是什么?
  13. android 播放wav代码,播放简短的.wav文件 - Android
  14. SQL审核 | SQLE-SQL审核平台体验报告
  15. JS JavaScript入门
  16. 腾讯官方披露,TDSQL十年自主可控之路(附PDF)
  17. easyui复杂表单_jQuery EasyUI 表单 – 创建树形下拉框(ComboTree) | 菜鸟教程
  18. SAP中采购订单收货和发票收据选项的控制逻辑分析
  19. 广告精准投放的新出路为何?
  20. decode函数的用法(decode函数的用法python二进制)

热门文章

  1. 百度开发者中心BAE新建Java应用
  2. 软件测试最好考研的学校,八点之后起床,考研失败率99%?测一测你的考研成功率有多少!...
  3. Linux日志批量删除
  4. 随笔 - 《阿里巴巴产品经理面试之必问列表》- 20201210
  5. HotSpot的算法细节实现(一)
  6. 网站首页滚动图片的后台管理
  7. 根链(Rootstock)是什么?
  8. 解析企业数据中心整体解决方案
  9. python期末复习指南
  10. neutron网络服务部署