概述


随着开发技术的发展,几乎所有主流的开发语言都有自己的包管理工具。Node 开发有 npm、Android 开发有 Gradle,Flutter 也有自己的 Dart Packages 仓库。插件的开发和复用能够提高开发效率,降低工程的耦合度,像网络请求(http)、用户授权(permission_handler)等客户端开发常用的功能模块,我们只需要引入对应插件就可以为项目快速集成相关能力,从而专注于具体业务功能的实现。


除了使用仓库中的流行组件以外,在 Flutter 项目开发过程中面对通用业务逻辑拆分、或者需要对原生能力封装等场景时,开发者仍然需要开发新的组件。本文以一个具体的 native_image_view 插件为例,将从 Flutter 组件的创建、开发、测试和发布等多个方面进行介绍,力图完整的展示整个 Flutter 组件的开发和发布流程。


Flutter 与 Native 通信


在 Flutter 插件开发过程中,几乎都会需要进行 Flutter 与 Native 端的数据交互,因此在进行插件开发之前,我们先简单了解下 Platform Channel 机制


Flutter 与 Native 的通信是通过 Platform Channel 实现的,它是一种 C/S 模型,其中 Flutter 作为 Client,iOS 和 Android 平台作为 Host,Flutter 通过该机制向 Native 发送消息,Native 在收到消息后调用平台自身的 API 进行实现,然后将处理结果再返回给 Flutter 页面。



Flutter 中的 Platform Channel 机制提供了三种交互方式:


  • BasicMessageChannel :用于传递字符串和半结构化信息;

  • MethodChannel :用于传递方法调用和处理回调;

  • EventChannel:用于数据流的监听与发送。


这三种 channel 虽然用途不同,但都包含了三个重要的成员变量:


(1)String name


表示 channel 的名字,在一个项目中可能会有很多的 channel,每个 channel 都应该使用唯一的命名标识,否则可能会被覆盖。推荐的命名方式是组织名称加插件的名称,例如:com.tencent.game/native_image_view,如果一个插件中包含了多个 channel 可再根据功能模块进一步进行区分。


(2)BinaryMessager messager


作为 Native 与 Flutter 通信的载体,能够将 codec 转换后的二进制数据在 Native 与 Flutter 之间进行传递。每个 channel 在初始化时都要生成或提供对应的 messager,如果 channel 注册了对应的 handler,则 messager 会维护一个 name 与 handler 的映射关系。


Native 平台在收到对方发来的消息后,meesager 会将消息内容分发给对应的 handler 进行处理,在处理完成后还可以通过回调方法 result 将处理结果返回给 Flutter。



(3)MessageCodec/MethodCodec codec


用于 Native 与 Flutter 通信过程中的编解码,在发送方能够将 Flutter(或 Native)的基础类型编码为二进制进行数据传输,在接收方 Native(或 Flutter)将二进制转换为 handler 能够识别的基础类型。


注:本文实现的 native_image_share 插件仅用到了最为常用的 MethodChannel 通信,Flutter 通过 MethodChannel 将远程图片地址或本地图片文件名传递给原生侧,iOS 和 Android 平台获取到图片后转换为二进制并通过 result 返回。更多关于 MessageChannel 和 EventChannel 的示例可以文末提供参考扩展阅读。


插件创建


Flutter 组件根据是否包含原生代码可分为两种:


  • Flutter Package(包):仅包含 dart 代码,一般是对 flutter 特定功能的封装实现,例如用于网络请求的 http 包。

  • Flutter Plugin(插件):除了 dart 代码之外,还包含了 Android 和 iOS 平台的代码实现,常用于将客户端原生的能力进行封装,然后提供给 flutter 项目使用。例如用于判断键盘可见状态的 flutter_keyboard_visibility 插件,就是分别在 iOS 和 Android 端监听了键盘的打开和关闭事件,然后将对应事件通过 Platform Channel 传递给 Flutter 项目。



Flutter 插件可以通过 Android Studio 创建(需要在 Android Studio 中先安装 Dart 和 Flutter 插件),或者使用命令行创建。


1. 创建 Dart 包


使用--template=package 声明创建的是只包含 dart 代码的 package。



复制代码



lib 目录用于存放 package 的代码实现,Flutter 脚手架会自动生成一个与 package 同名的 dart 文件。

pubspec.yaml 文件想必做过 Flutter 开发的同学都非常熟悉,我们开发 package 所依赖的 package 或者 plugin 都需要在该文件中声明。


2. 创建 Flutter 插件


  • 使用--template=plugin 声明创建的是同时包含了 iOS 和 Android 代码的 plugin;

  • 使用--org 选项指定组织,一般采用反向域名表示法;

  • 使用-i 选项指定 iOS 平台开发语言,objc 或者 swift;

  • 使用-a 选项指定 Android 平台开发语言,java 或者 kotlin。

flutter create --template=plugin --org com.tencent.game -i objc -a java native_image_view


相比 Package,我们可以看到 Plugin 多出了一些目录,android 目录用于 Android 平台的代码实现,ios 目录用于 iOS 平台的代码实现,example 目录用于该组件的调试。


注:Flutter 脚手架在创建 Plugin 时默认实现了一个获取系统版本号的示例,该示例的原理是分别在 iOS 和 Android 平台获取到系统版本号,然后通过 MethodChannel 调用返回给 Flutter 平台显示。


插件开发


Plugin 和 Package 的开发和发布流程基本一致,相比之下 Plugin 还涉及到 iOS 和 Android 的开发,实现起来要更加复杂一些。


在 Flutter 嵌入原生项目的场景中,比较常见的一个问题是:Flutter 和原生项目中都使用了同一张图片时,两侧会分别进行存储,即该图片会被存储两次。不同于 Weex、Hippy 等基于 JS 的跨平台框架是依赖于原生进行图片的获取和显示,Flutter 是自行进行图片的管理并直接通过 Skia 引擎直接进行绘制的。


针对这一问题,本文将开发一个 Flutter 插件(native_image_view),把 Flutter 图片的下载和缓存工作交给 Native 实现,Flutter 端则仅负责图片的绘制。此外,我们还可以定义一个特殊协议,用于处理本地图片的调用,同时解决 Flutter 无法复用原生项目本地图片的问题。



注:本文开发的插件仅用于介绍插件的开发和发布流程,不建议在生成环境中直接使用,关于图片二次缓存问题还可以参考扩展阅读中关于 Texture(外接纹理)的文章。