移动 前端 Python Android JavaFlutter

Flutter Plugin 的编写使用

2020-07-27  本文已影响0人  zcwfeng

Flutter Plugin 资料

[TOC]

前言

在Flutter中,如果我们需要打印日志,如果不进行自定义,我们只能使用自带的print()或者debugPrint()方法进行打印,
但是这两种打印,日志都是默认Info层级的日志,很不友好,所以如果需要日志打印层级分明,我们就需要自定义一个
日志打印组件,但是我希望这个打日志的组件也可以以后在其他项目里直接拿来使用.这就需要我们来开发一个日志的插件了,
再比如我们想在Flutter里面获取Android设备的信息,或者就是想用Native实现一个功能,然后能在Flutter里面使用. 等等...

什么是插件

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

一个最小化的package包含了两部分:

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

package的种类

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

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

开发步骤

开发 Dart packages

要创建Dart包,请使用--template=package 来执行 flutter create

flutter create --template=package hello

这将在hello/文件夹下创建一个具有以下专用内容的package工程:

开发 plugin packages

Step 1: 创建 package

flutter create --org com.example --template=plugin hello

flutter create --template=plugin -i swift -a kotlin hello

Step 2: 实现包 package

在Android Studio中编辑Android平台代码之前,首先确保代码至少已经构建过一次(例如,从IntelliJ运行示例应用程序或在终端执行cd hello/example; flutter build apk)

  1. 启动Android Studio
  2. 在’Welcome to Android Studio’对话框选择 ‘Import project’, 或者在菜单栏 ‘File > New > Import Project…‘,然后选择hello/example/android/build.gradle文件.
  3. 在’Gradle Sync’ 对话框, 选择 ‘OK’.
  4. 在’Android Gradle Plugin Update’ 对话框, 选择 ‘Don’t remind me again for this project’.
    您插件的Android平台代码位于 hello/java/com.yourcompany.hello/​HelloPlugin.

您可以通过按下 ▶ 按钮从Android Studio运行示例应用程序.

添加文档

建议将以下文档添加到所有软件包:

  1. README.md:介绍包的文件
  2. CHANGELOG.md 记录每个版本中的更改
  3. LICENSE 包含软件包许可条款的文件

发布 packages

一旦你实现了一个包,你可以在Pub上发布它 ,这样其他开发人员就可以轻松使用它

在发布之前,检查pubspec.yamlREADME.md以及CHANGELOG.md文件,以确保其内容的完整性和正确性。

然后, 运行 dry-run 命令以查看是否都准备OK了:

flutter packages pub publish --dry-run

最后, 运行发布命令:

flutter packages pub publish

Plugin 通信原理

flutter.png

实际上,Flutter的上层能力都是Engine提供的。Flutter正是通过Engine将各个Platform的差异化抹平。而我们今天要讲的Plugin,正是通过Engine提供的Platform Channel实现的通信。

flutter_channel.png

通过上图,我们看到Flutter App是通过Plugin创建的Platform Channel调用的Native APIs。

Platform Channel:

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

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

理解Platform Channel工作原理

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

Channel name

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

消息信使:BinaryMessenger

binary_messenger.png

虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。

消息编解码器:Codec

codec.png

​ 消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec。

MessageCodec

​ MessageCodec用于二进制格式数据与基础数据之间的编解码。BasicMessageChannel所使用的编解码器就是MessageCodec。
​ Android中,MessageCodec是一个接口,定义了两个方法:encodeMessage接收一个特定的数据类型T,并将其编码为二进制数据ByteBuffer,而decodeMessage则接收二进制数据ByteBuffer,将其解码为特定数据类型T。iOS中,其名称为FlutterMessageCodec,是一个协议,定义了两个方法:encode接收一个类型为id的消息,将其编码为NSData类型,而decode接收NSData类型消息,将其解码为id类型数据。
​ MessageCodec有多种不同的实现:

BinaryCodec

BinaryCodec是最为简单的一种Codec,因为其返回值类型和入参的类型相同,均为二进制格式(Android中为ByteBuffer,iOS中为NSData)。实际上,BinaryCodec在编解码过程中什么都没做,只是原封不动将二进制数据消息返回而已。或许你会因此觉得BinaryCodec没有意义,但是在某些情况下它非常有用,比如使用BinaryCodec可以使传递内存数据块时在编解码阶段免于内存拷贝。

StringCodec

StringCodec用于字符串与二进制数据之间的编解码,其编码格式为UTF-8。

MethodCodec

​ MethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel和EventChannel所使用的编解码器均为MethodCodec。
​ 与MessageCodec不同的是,MethodCodec用于MethodCall对象的编解码,一个MethodCall对象代表一次从Flutter端发起的方法调用。MethodCall有2个成员变量:String类型的method代表需要调用的方法名称,通用类型(Android中为Object,iOS中为id)的arguments代表需要调用的方法入参。
​ 由于处理的是方法调用,故相比于MessageCodec,MethodCodec多了对调用结果的处理。当方法调用成功时,使用encodeSuccessEnvelope将result编码为二进制数据,而当方法调用失败时,则使用encodeErrorEnvelope将error的code、message、detail编码为二进制数据。
​ MethodCodec有两种实现:

消息处理器:Handler

​ 当我们接收二进制格式消息并使用Codec将其解码为Handler能处理的消息后,就该Handler上场了。Flutter定义了三种类型的Handler,与Channel类型一一对应。我们向Channel注册一个Handler时,实际上就是向BinaryMessager注册一个与之对应的BinaryMessageHandler。当消息派分到BinaryMessageHandler后,Channel会通过Codec将消息解码,并传递给Handler处理。

. MessageHandler

​ MessageHandler用户处理字符串或者半结构化的消息,其onMessage方法接收一个T类型的消息,并异步返回一个相同类型result。MessageHandler的功能比较基础,使用场景较少,但是其配合BinaryCodec使用时,能够方便传递二进制数据消息。

MethodHandler

​ MethodHandler用于处理方法的调用,其onMessage方法接收一个MethodCall类型消息,并根据MethodCall的成员变量method去调用对应的API,当处理完成后,根据方法调用成功或失败,返回对应的结果。

StreamHandler
streamhandler.png

理解消息编解码过程

​ 在官方文档《Writing custom platform-specific code with platform channels》中的获取设备电量的例子中我们发现,Android端的返回值是java.lang.Integer类型的,而iOS端返回值则是一个NSNumber类型的(通过NSNumber numberWithInt:获取)。而到了Flutter端时,这个返回值自动"变成"了dart语言的int类型。那么这中间发生了什么呢?
​ Flutter官方文档表示,standard platform channels使用standard messsage codec对message和response进行序列化和反序列化,message与response可以是booleans, numbers, Strings, byte buffers,List, Maps等等,而序列化后得到的则是二进制格式的数据。
​ 所以在上文提到的例子中,java.lang.Integer或NSNumber类型的返回值先是被序列化成了一段二进制格式的数据,然后该数据传递到传递到flutter侧后,被反序列化成了dart语言中的int类型的数据。
​ Flutter默认的消息编解码器是StandardMessageCodec,其支持的数据类型如下:

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

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
int, if 64 bits not enough java.math.BigInteger FlutterStandardBigInteger
double java.lang.Double NSNumber numberWithDouble:
String j ava.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
msg_encode.png

理解消息传递过程

​ 消息是如何从Flutter端传递到Platform端的呢?接下来我们以一次MethodChannel的调用为例,去理解消息的传递过程。

Dart层

​ 当我们在Flutter端使用MethodChannel的invokeMethod方法发起一次方法调用时,就开始了我们的消息传递之旅。invokeMethod方法会将其入参message和arguments封装成一个MethodCall对象,并使用MethodCodec将其编码为二进制格式数据,再通过BinaryMessages将消息发出。(注意,此处提到的类名与方法名均为dart层的实现)
​ 上述过程最终会调用到ui.Window的_sendPlatformMessage方法,该方法是一个native方法,其实现在native层,这与Java的JNI技术非常类似。我们向native层发送了三个参数:

Native层

​ 到native层后,window.cc的SendPlatformMessage方法接受了来自dart层的三个参数,并对它们做了一定的处理:dart层的回调callback封装为native层的PlatformMessageResponseDart类型的response;dart层的二进制数据data转化为std::vector<uint8_t>类型数据data;根据response,data以及Channel名称name创建一个PlatformMessage对象,并通过dart_state->window()->client()->HandlePlatformMessage方法处理PlatformMessage对象。
​ dart_state->window()->client()是一个WindowClient,而其具体的实现为RuntimeController,RuntimeController会将消息交给其代理RuntimeDelegate处理。
​ RuntimeDelegate的实现为Engine,Engine在处理Message时,会判断该消息是否是为了获取资源(channel等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用Engine::Delegate的OnEngineHandlePlatformMessage方法。
​ Engine::Delegate的具体实现为Shell,其OnEngineHandlePlatformMessage接收到消息后,会向PlatformTaskRunner添加一个Task,该Task会调用PlatformView的HandlePlatformMessage方法。值得注意的是,Task中的代码执行在Platform Task Runner中,而之前的代码均执行在UI Task Runner中。

dart_2_native.png
消息处理

​ PlatformView的HandlePlatformMessage方法在不同平台有不同的实现,但是其基本原理是相同的。

. PlatformViewAndroid

​ PlatformViewAndroid的是Platformview的子类,也是其在Android端的具体实现。当PlatformViewAndroid接收到PlatformMessage类型的消息时,如果消息中有response(类型为PlatformMessageResponseDart),则生成一个自增长的response_id,并以response_id为key,response为value存入字典pending_responses_中。接着,将channel和data均转化为Java可识别的数据,通过JNI向Java层发起调用,将response_id、channel和data传递过去。
​ Java层中,被调用的代码为FlutterNativeView (BinaryMessager的具体实现)的handlePlatformMessage,该方法会根据channel找到对应的BinaryMessageHandler并将消息传递给它处理。其具体处理过程我们已经在上文中详细分析过了,此处不再赘述。
​ BinaryMessageHandler处理完成后,FlutterNativeView会通过JNI调用native的方法,将response_data和response_id传递到native层。
​ native层,PlatformViewAndroid的InvokePlatformMessageResponseCallback接收到了respond_id和response_data。其先将response_data转化为二进制结果,并根据response_id,从panding_responses_中找到对应的PlatformMessageResponseDart对象,调用其Complete方法将二进制结果返回。

plat_android.png
PlatformViewIOS

​ PlatformViewIOS是PlatformView的子类,也是其在iOS端的具体实现,当PlatformViewIOS接收到message时会交给PlatformMessageRouter处理。
​ PlatformMessageRouter通过PlatformMessage中的channel找到对应的FlutterBinaryMessageHandler,并将二进制消息其处理,消息处理完成后,直接调用PlatformMessage对象中的PlatformMessageResponseDart对象的Complete方法将二进制结果返回。

结果回传:从Platform到Flutter

​ PlatformMessageResponseDart的Complete方法向UI Task Runner添加了一个新的Task,这个Task的作用是将二进制结果从native的二进制数据类型转化为Dart的二进制数据类型response,并调用dart的callback将response传递到Dart层。
​ Dart层接收到二进制数据后,使用MethodCodec将数据解码,并返回给业务层。至此,一次从Flutter发起的方法调用就完整结束了。

上一篇下一篇

猜你喜欢

热点阅读