iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Flutter插件开发-(进阶篇)
  • 891
分享到

Flutter插件开发-(进阶篇)

androidflutterandroidstudio 2023-09-03 12:09:23 891人浏览 薄情痞子
摘要

一、概述 Flutter也有自己的dart Packages仓库。插件的开发和复用能够提高开发效率,降低工程的耦合度,像网络请求(Http)、用户授权(permission_handler)等客户端开发常用的功能模块,我们只需要引入对应插件

一、概述

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,iOSAndroid平台作为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插件),或者使用命令行创建。

  • 创建Flutter插件

flutter create --org com.qidian.image --template=plugin --platforms=android,ios -i objc -a java native_image_view 

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

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

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

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

lib目录用于存放package的代码实现,Flutter脚手架会自动生成一个与package同名的dart文件。
pubspec.yaml文件想必做过Flutter开发的同学都非常熟悉,我们开发package所依赖的package或者plugin都需要在该文件中声明。

四、插件开发 

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

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


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

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

1. Flutter端开发

我们首先在Flutter端声明了插件的MethodChannel,然后在initState方法中通过invokeMethod(方法名,参数)发起了对Native端的方法调用,在build方法中先显示图片的打底图,待图片数据返回后再调用setState,使用Image.memory方法将二进制数据绘制成图片显示。

native_image_view.dart:

class _NativeImageViewState extends State {  Uint8List _data;  static const MethodChannel _channel =      const MethodChannel('com.tencent.game/native_image_view');  @override  void initState() {    super.initState();    loadImageData();  }  loadImageData() async {    _data = await _channel.invokeMethod("getImage", {"url": widget.url});    setState(() {});  }  @override  Widget build(BuildContext context) {    return _data == null        ? Container(            color: Colors.grey,            width: widget.width,            height: widget.height,          )        : Image.memory(            _data,            width: widget.width,            height: widget.height,            fit: BoxFit.cover,          );

2. Native端开发

(1)iOS开发

插件的iOS平台使用SDWEBImage组件进行网络图片的下载和缓存,因此在native_image_view.podspec文件中声明依赖。

s.dependency 'Flutter's.dependency 'SDWebImage's.platform = :ios, '8.0'

Flutter脚手架自动为我们生成了NativeImageViewPlugin.m文件和reGISterWithRegistrar方法,该方法是组件执行的入口,会被Flutter的插件管理器自动调用。

我们在该方法中使用与Flutter端相同的name创建MethodChannel,并创建插件对象的实例,用于处理Flutter端的方法调用。handleMethodCall方法会在MethodChannel收到Flutter端的方法调用后被触发,开发者可以通过FlutterMethodCall获取方法名和参数,通过FlutterResult返回图片内容。

NativeImageViewPlugin.m:

#import "NativeImageViewPlugin.h"#import @implementation NativeImageViewPlugin//组件注册接口,Flutter自动调用+ (void)registerWithRegistrar:(NSObject*)registrar {  FlutterMethodChannel* channel = [FlutterMethodChannel      methodChannelWithName:@"com.tencent.game/native_image_view"            binaryMessenger:[registrar messenger]];  NativeImageViewPlugin* instance = [[NativeImageViewPlugin alloc] init];  [registrar addMethodCallDelegate:instance channel:channel];}- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {  if ([@"getImage" isEqualToString:call.method]) {      [self getImageHandler:call result:result];  } else {      result(FlutterMethodNotImplemented);  }}- (void)getImageHandler:(FlutterMethodCall*)call result:(FlutterResult)result{  if(call.arguments != nil && call.arguments[@"url"] != nil){      NSString *url = call.arguments[@"url"];      if([url hasPrefix:@"localImage://"]){        //获取本地图片        NSString *imageName = [url stringByReplacinGoccurrencesOfString:@"localImage://" withString:@""];        UIImage *image = [UIImage imageNamed:imageName];        if(image != nil){            NSData *imgData = UIImageJPEGRepresentation(image,1.0);            result(imgData);        }else{            result(nil);        }      }else {        //获取网络图片        UIImage *image = [[SDImageCache sharedImageCache] imageFromCacheForKey:url];        if(!image){          //本地无缓存,下载后返回图片          [[SDWebImageDownloader sharedDownloader]            downloadImageWithURL:[[NSURL alloc] initWithString:url]            completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {              if(finished){                result(data);                [[SDImageCache sharedImageCache] storeImage:image forKey:url completion:nil];              }            }];        }else{          //本地有缓存,直接返回图片          NSData *imgData = UIImageJPEGRepresentation(image,1.0);          result(imgData);        }      }  }}@end

在处理Flutter端发起的图片调用时,首先判断Flutter请求的是本地还是网络图片,如果是本地图片则直接根据UIImage对象读取图片的二进制数据返回;如果是网络图片则先判断是否存在本地缓存,有缓存直接返回,无缓存则需要先下载图片然后再返回数据。 

(2)Android开发

插件的Android平台使用Glide组件进行网络图片的下载和缓存,需要在build.gradle文件中声明依赖。

dependencies { implementation 'com.GitHub.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'}

为了兼容历史版本,Android端的插件需要在onAttachedToEngine和registerWith方法中实现相同的MethodChannel注册与监听的逻辑,onMethodCall用于处理Flutter中的方法调用,也提供了与iOS平台类似的MethodCall和Result对象。

android/src/main/xxxx/NativeImageViewPlugin.java:

//新的插件注册接口@Overridepublic void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {  channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "com.tencent.game/native_image_view");  channel.setMethodCallHandler(this);  setContext(flutterPluginBinding.getApplicationContext());}@Overridepublic void onDetachedFromEngine(FlutterPluginBinding binding) {  channel.setMethodCallHandler(null);}// Flutter-1.12之前的插件注册接口,功能与onAttachedToEngine一样public static void registerWith(Registrar registrar) {  NativeImageViewPlugin plugin = new NativeImageViewPlugin();  plugin.setContext(registrar.context());  final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.tencent.game/native_image_view");  channel.setMethodCallHandler(plugin);}@Overridepublic void onMethodCall(final MethodCall call,final Result result) {  if (call.method.equals("getImage")) {    getImageHandler(call,result);  } else {    result.notImplemented();  }}

Android端的代码实现逻辑与iOS一致,也是先判断Flutter调用的是本地还是网络图片,对于本地图片先根据文件名获取到图片的Bitmap,然后转成byte数组返回;对于网络图片的缓存和下载基于Glide组件实现,在获取到文件缓存或下载路径后,再将文件读取为byte数组返回。

public void getImageHandler(final MethodCall call,final Result result){  HashMap map = (HashMap) call.arguments;  String urlStr = map.get("url").toString();  Uri uri = Uri.parse(urlStr);  if("localImage".equals(uri.getScheme())){    String imageName = uri.getHost();    int lastIndex = imageName.lastIndexOf(".");    if(lastIndex > 0){      imageName = imageName.substring(0,lastIndex);    }    String imageUri = "@drawable/"+imageName;    int imageResource = context.getResources().getIdentifier(imageUri, null, context.getPackageName());    if(imageResource > 0){      Bitmap bmp = BitmapFactory.decodeResource(context.getResources(),imageResource);      ByteArrayOutputStream stream = new ByteArrayOutputStream();      bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);      byte[] byteArray = stream.toByteArray();      result.success(byteArray);    }else{      result.error("NOT_FOUND","file not found",call.arguments);    }  }else {    Glide.with(context).download(urlStr).into(new CustomTarget() {      @Override      public void onResourceReady(@NonNull File resource, @Nullable Transition transition) {        byte[] bytesArray = new byte[(int) resource.length()];        try {          FileInputStream fis = new FileInputStream(resource);          fis.read(bytesArray);          fis.close();          result.success(bytesArray);        } catch (IOException e) {          e.printStackTrace();          result.error("READ_FAIL",e.toString(),call.arguments);        }      }      @Override      public void onLoadFailed(@Nullable Drawable errorDrawable) {        super.onLoadFailed(errorDrawable);        result.error("LOAD_FAIL","image download fail",call.arguments);      }      @Override      public void onLoadCleared(@Nullable Drawable placeholder) {        result.error("LOAD_CLEARED","image load clear",call.arguments);      }    });  }}

五、插件测试

Flutter脚手架在创建插件的时候自动生成了example项目,该项目通过指定插件path的方式引用了我们正在开发中的组件,让我们在发布插件之前可以进行充分的测试。

native_image_view:

path: ../

example项目除了开发调试之外,还是一种很好的插件使用示例。相比于文档,很多开发者更喜欢直接看插件example的代码实现。我们在main.dart中展示了网络图片的使用,本地图片需要原生项目中存在对应文件才可以。

main.dart:

String url = "";//String url = "localImage://xxx.jpeg";@overrideWidget build(BuildContext context) {  return MaterialApp(    home: Scaffold(      appBar: AppBar(        title: Text('example'),      ),      body: Center(      child: NativeImageView(        url: url,        width: 300,        height: 200,      ),    ),  ));}

 六、插件发布

插件开发完成后就进入了发布环节,为了便于后续维护和用户反馈问题,我们将插件在github上进行维护,并在插件的pubspec.yaml文件中填写仓库地址

name: native_image_view

description: 该组件提供了一种方式,可以让flutter通过methodChannel调用原生的本地和网络图片的加载

version: 0.0.1

repository: 

在提交仓库之前,我们需要先运行dry-run命令检查组件目前是否符合发布要求。

flutter pub publish --dry-run

 Flutter脚手架为我们创建的LICENSE文件是空的,需要开发者自行填写插件的开源协议。如果不填写的话dry-run不会提示,但在仓库发布那一步还是会报错。

上一篇有教如何建立LICENSE文件,不再赘述。

来源地址:https://blog.csdn.net/RreamigOfGirls/article/details/130224297

--结束END--

本文标题: Flutter插件开发-(进阶篇)

本文链接: https://www.lsjlt.com/news/392126.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Flutter插件开发-(进阶篇)
    一、概述 Flutter也有自己的Dart Packages仓库。插件的开发和复用能够提高开发效率,降低工程的耦合度,像网络请求(http)、用户授权(permission_handler)等客户端开发常用的功能模块,我们只需要引入对应插件...
    99+
    2023-09-03
    android flutter android studio
  • Flutter插件开发-(基础篇)
          在开发flutter项目的时分通常会运用一些三方的的packages或许plugin,二者的区别:packages主要是包括的Dart代码块,而plugin则包括iOS和android的代码。 因此来说创立plugin和pack...
    99+
    2023-10-25
    flutter android vscode
  • 【Flutter入门到进阶】Flutter基础篇---弹窗Dialog
    1 AlertDialog 1.1 说明         最简单的方案是利用AlertDialog组件构建一个弹框 1.2 示例 void alertDialog(BuildContext context) async {  var res...
    99+
    2023-10-20
    flutter android 开发语言
  • CMS插件开发 进阶教程:打造强大功能
    扩展插件功能:自定义工具的力量 CMS 插件的核心是扩展网站的功能,但您能做的不仅仅是添加基本功能。通过创建自定义工具,您可以提供独特的解决方案,满足用户特定的需求。例如,您可以构建以下工具: 内容过滤和排序器 定制化数据表格 交互式...
    99+
    2024-02-15
    CMS 插件开发 进阶教程 自定义工具 用户体验
  • java开发Activiti进阶篇流程实例详解
    目录1.流程实例1.1 什么是流程实例1.2 业务管理1.3 流程实例的挂起和激活1.3.1 全部流程挂起1.3.2 单个实例挂起1.流程实例 1.1 什么是流程实例 流程实例(Pr...
    99+
    2022-11-13
    java Activiti流程实例 java Activiti
  • 如何进行JQuery插件的开发
    这期内容当中小编将会给大家带来有关如何进行JQuery插件的开发,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。【前言】jQuery已经被广泛使用,凭借其简洁的API,对D...
    99+
    2024-04-02
  • Flutter 学习路线图!跨平台开发必备,不可错过的Flutter进阶历程!
    Flutter 学习路线图如果你真的觉得很难,坚持不了了,那就放弃,既然放弃了就不要抱怨没有得到。选择你热爱的,坚持你选择的,不抱怨放弃的。前言Flutter越来越火,学习Flutter的人越来越多,对于刚接触Flutter的人来说最重要的...
    99+
    2023-06-04
  • 如何利用elasticsearch插件进行开发
    如何利用elasticsearch插件进行开发?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。检索引擎Elasticsearch支持插件模式。有些时候你可能须要安...
    99+
    2023-05-31
    elasticsearch
  • Spring零基础到进阶之鸿蒙开篇
    目录Spring是什么1.什么是容器?2.什么是IOC?3.理解Spring IoC4.了解DISpring是什么 用一句简单的话来概括Spring:Spring是包含了众多工具方法...
    99+
    2024-04-02
  • SEO高手进阶:CMS插件进阶指南,提升网站排名
    插件选择 选择合适的插件对于SEO成功至关重要。以下列出了一些最有效的插件类型: SEO优化:这些插件提供工具来优化标题、元描述、页眉和网站其他方面的SEO元素。 网站速度提升:它们通过压缩图像、减少重定向和缓存页面来提高网站速度。 安...
    99+
    2024-04-02
  • CMS插件开发 工具和资源:加速开发进程
    CMS插件是扩展CMS功能和定制其行为的有力工具。通过利用强大的开发工具和资源,开发者可以显著加快插件开发流程,提高效率和生产力。以下是一系列经过验证的工具和资源,可帮助CMS插件开发者加速其开发进程: 开发平台: Xdebug:一种...
    99+
    2024-02-15
    CMS 插件开发 工具 资源 加速开发
  • TypeScript赋能VUE:组件开发的进阶之路
    TypeScript以其强大的类型系统和静态分析能力,为Vue组件开发带来了显著的提升。本文深入探讨TypeScript如何赋能Vue组件开发,提供进阶指导,助力开发人员打造更健壮、可维护性更高的应用程序。 一、类型检查与静态分析 Typ...
    99+
    2024-03-12
    引言
  • Java并发编程进阶之线程控制篇
    目录一、线程的基本概念1.并行和并发2.进程和线程二、线程的运行状态三、线程操作实践1.线程两种定义方法2.启动线程3.同时定义和启动线程4.线程弹出与暂停5.线程等待与唤醒6.线程...
    99+
    2024-04-02
  • 怎么进行WordPress插件开发之创建、停用、删除插件
    这篇文章主要介绍了怎么进行WordPress插件开发之创建、停用、删除插件,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。插件存放目录wp-content/plugins创建一...
    99+
    2023-06-06
  • python3-开发进阶Flask的基础
     一、概述 最大的特点:短小精悍、可拓展强的一个Web框架。注意点:上下文管理机制,依赖wsgi:werkzurg 模块 二、前奏学习werkzurg 先来回顾一个知识点:一个类加括号会执行__init__方法,一个对象加括号执行__ca...
    99+
    2023-01-30
    进阶 基础 Flask
  • WordPress插件开发教程1:开发第一个WordPress插件
    一、创建一个插件         第一步:在 wp-content \ plugins 目录新建一个目录,随便起个名字,比如:my-first-plugin。         第二步:进入 my-first-plugin 目录,新建一个PH...
    99+
    2023-09-10
    Wordpress WordPress插件开发教程 WordPress插件开发 WordPress插件教程 WordPress开发教程
  • vue2组件进阶与插槽详解(推荐!)
    目录一、组件进阶1.v-model语法2.ref与$ref语法3.dynamic动态组件4.this.$nextTick()二、匿名|具名|作用域插槽插槽概念:1.匿名插槽2.具名插...
    99+
    2023-02-08
    vue插槽详解 插槽vue vue2组件进阶
  • iOS开发进阶(二):走近iOS原生开发
    文章目录 一、前言二、知识储备三、 Object-C四、启动流程五、拓展阅读 一、前言 在应用 uni-app 进行跨平台APP开发过程中,发现并不支持视频播放小窗功能,且插件市场提供的插件用户体验不好,遂决定自行开发 un...
    99+
    2023-08-18
    ios cocoa macos
  • 【Python】【进阶篇】六、Tkinter的Text文本框控件
    六、Tkinter的Text文本框控件 Text 文本控件是 Tkinter 中经常使用的控件,与 Entry 控件相比,Text 控件用于显示和编辑多行文本,而 Entry 控件则适合处理单行文本。...
    99+
    2023-09-14
    python 开发语言
  • Python全栈开发之Django进阶
    No.1 静态文件处理 项目中CSS、JS、图片都属于静态文件,一般会将静态文件存到一个单独目录中,便于管理,在HTML页面调用时,需要指定静态文件的路径,Django提供了一种解析静态文件的机制,文件可以放在项目目录下,也可以放在应用目录...
    99+
    2023-01-31
    进阶 Python Django
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作