用Kotlin撸一个图片压缩插件-实战篇(三)

简述: 由于个人原因,已经有很长一段时间没有写过文章,有句话是那么说的只要开始就不会太晚,所以我们开始《用Kotlin撸一个图片压缩插件》系列文章最后一篇实战篇。实际上我已经把源码发布到了GitHub,代码很简单。有了前两篇文章的基础,这篇文章将会使用Kotlin从零开始带你撸个图片压缩插件。

一、开发前期准备工作

  • 1、访问TinyPng官网注册TinyPng开发者账号,拿到TinyPng ApiKey,整个过程只需简单注册验证即可。

  • 2、由于本项目图片压缩框架是基于TinyPng的图片压缩API来实现的,所以需要在TinyPng官网提供了develop开发库,可以找到相应Java的jar,为了方便下载这里就直接贴出地址了:TinyPng依赖包下载

  • 3、由于图片插件使用到GUI,插件GUI采用的是Java中的Swing框架搭建,具体可以去复习相关Swing的知识点,当然只需要大概了解即可,毕竟这个不是重点。

  • 4、需要去掌握插件开发的基础知识,由于本篇文章是实战篇就不去细讲插件基础知识,具体详情可参照该系列的第二篇文章用Kotlin撸一个图片压缩插件-插件基础篇(二)

  • 5、需要有Kotlin的基本开发知识,比如Kotlin中扩展函数的封装,Lambda表达式,函数式API,IO流API的使用

二、图片压缩插件基本功能点

图片压缩插件主要支持如下两大功能:

  • 1、支持指定图片源输入目录批量压缩到一个指定的输出目录。

  • 2、支持在AndroidStudio项目中直接选中指定的一个或多个图片,右键点击直接压缩。

三、实现思路分析

实现的整体思路:首先我们需要找到实现关键点,然后从关键点一步步向外扩展延伸,那么实现图片压缩的插件的关键点在哪里,肯定毫无疑问是图片压缩API,也就是TinyPng API函数调用实现。

Tinify.fromFile(inputFile).toFile(inputFile)
复制代码

通过以上的TinyPng API就可以找到关键点,一个是输入文件另一个则是输出文件,那么我们这个图片压缩插件的所有实现都是围绕着如何通过一个简单的方式指定一个输入文件或目录和一个输出文件或目录。

没错就是这么简单,那么我们一起来分析下上面两大功能实现思路其实也很简单:

  • 功能点一: 就是通过Swing框架中的JFileChooser组件,打开并指定一个图片输入文件或目录和一个图片压缩后的输出文件或目录即可。

  • 功能点二: 通过Intellij Idea open api中的 DataKeys.VIRTUAL_FILE_ARRAY.getData(this)拿到当前选中的Virtual Files,也就是当前选中的文件把选中的文件当做输入文件,然后图片压缩后文件直接输出到源文件中即可。

注意: 由于Tiny.fromFile().toFile()内部源码实际上通过OkHttp发送图片压缩的网络请求,而且内部采用的方式是同步请求的,但是在IDEA Plugin开发中主线程是不能执行耗时任务的,所以需要将该API方法调用放在异步任务中

四、代码结构和实现

  • action包:主要定义插件中的两个action,我们都知道在插件开发中Action是功能执行的入口,ImageSlimmingAction是前面说到第一个功能点批量压缩指定输入和输出目录的,RightSelectedAction是前面说过的第二个功能点在项目选中图中文件直接右键压缩的, 最后这两个Action都需要在plugin.xml中注册。
  <actions><action class="com.mikyou.plugins.image.slimming.action.ImageSlimmingAction" text="ImageSlimming"id="com.mikyou.plugins.image.slimming.action.ImageSlimmingAction"description="compress picture plugin" icon="/img/icon_image_slimming.png"><add-to-group group-id="MainToolBar" anchor="after" relative-to-action="Android.MainToolBarSdkGroup"/></action><action id="com.mikyou.plugins.image.action.rightselectedaction"class="com.mikyou.plugins.image.slimming.action.RightSelectedAction" text="Quick Slim Images"description="Quick Slim Images"><add-to-group group-id="ProjectViewPopupMenu" anchor="after" relative-to-action="ReplaceInPath"/></action></actions>
复制代码
  • extension包: 主要是定义了Kotlin中的扩展函数,一个是Boolean的扩展可以类似链式调用来替代if-else判断,另一个则是Dialog使用的扩展
//Boolean 扩展
sealed class BooleanExt<out T>object Otherwise : BooleanExt<Nothing>()//Nothing是所有类的子类,协变的类继承关系和泛型参数类型继承关系一致class TransferData<T>(val data: T) : BooleanExt<T>()inline fun <T> Boolean.yes(block: () -> T): BooleanExt<T> = when {this -> TransferData(block.invoke())else -> Otherwise
}inline fun <T> Boolean.no(block: () -> T): BooleanExt<T> = when {this -> Otherwiseelse -> TransferData(block.invoke())
}inline fun <T> BooleanExt<T>.otherwise(block: () -> T): T = when (this) {is Otherwise ->block()is TransferData ->this.data
}//Dialog扩展
fun Dialog.showDialog(width: Int = 550, height: Int = 400, isInCenter: Boolean = true, isResizable: Boolean = false) {pack()this.isResizable = isResizablesetSize(width, height)if (isInCenter) {setLocation(Toolkit.getDefaultToolkit().screenSize.width / 2 - width / 2, Toolkit.getDefaultToolkit().screenSize.height / 2 - height / 2)}isVisible = true
}fun Project.showWarnDialog(icon: Icon = UIUtil.getWarningIcon(), title: String, msg: String, positiveText: String = "确定", negativeText: String = "取消", positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null) {Messages.showDialog(this, msg, title, arrayOf(positiveText, negativeText), 0, icon, object : DialogWrapper.DoNotAskOption.Adapter() {override fun rememberChoice(p0: Boolean, p1: Int) {if (p1 == 0) {positiveAction?.invoke()} else if (p1 == 1) {negativeAction?.invoke()}}})
}
复制代码
  • helper包主要是用文件IO操作,由于两个Action都存在图片压缩操作,为了复用就直接把图片压缩API调用的实现操作抽出封装在ImageSlimmingHelper中。

  • ui包主要就是Swing框架中一些界面GUI的实现和交互。

四、实现的关键技术点

  • 关键点一: 插件开发中如何执行一个异步任务

IDEA Plugin开发和Android开发很类似,一些耗时的任务是不能直接在主线程执行的,需要在特定后台线程执行,否则会阻塞主线程。在intellij open api中有个Task.Backgroundable抽象类就是处理异步任务的。Backgroundable继承了Task类以及实现了PerformInBackgroundOption接口。具体使用很简单传入两个参数一个是Project对象和一个执行异步中hint提示文本,有四个回调函数分别为run(progress: ProgressIndicator)、onSuccess、onThrowable、onFinished.最后通过queue方法加入到异步任务队列中。为了方便调用将其封装成一个扩展函数来使用。

//创建后台异步任务的Project的扩展函数asyncTask
private fun Project.asyncTask(hintText: String,runAction: (ProgressIndicator) -> Unit,successAction: (() -> Unit)? = null,failAction: ((Throwable) -> Unit)? = null,finishAction: (() -> Unit)? = null
) {object : Task.Backgroundable(this, hintText) {override fun run(p0: ProgressIndicator) {runAction.invoke(p0)}override fun onSuccess() {successAction?.invoke()}override fun onThrowable(error: Throwable) {failAction?.invoke(error)}override fun onFinished() {finishAction?.invoke()}}.queue()
}
//asyncTask的使用project?.asyncTask(hintText = "正在压缩", runAction = {//执行图片压缩操作outputSameFile.yes {//针对右键选定图片情况,直接压缩当前目录选中图片,输出目录包括文件也是原来的inputFiles.forEach { inputFile -> Tinify.fromFile(inputFile.absolutePath).toFile(inputFile.absolutePath) }}.otherwise {inputFiles.forEach { inputFile -> Tinify.fromFile(inputFile.absolutePath).toFile(getDestFilePath(model, inputFile.name)) }}}, successAction = {successAction?.invoke()}, failAction = {failAction?.invoke("TinyPng key存在异常,请重新输入")})
复制代码
  • 关键点二: 插件开发中如何获取当前选中的文件或目录

在插件开发中如何获得当前选中文件,实际上open api提供了类似DataContext数据上下文环境,我们需要去拿到文件集合对象就需要先找到文件管理的窗口对象,还记得上篇博客中说到的AnActionEvent对象是插件与IDEA交互通信的一个媒介,通过AnActionEvent内部的dataContext的getData方法,传入对应的DataKey对象获得相应的窗口对象。在CommonDataKey中有一个DataKey<VirtualFile[]>,通过传入当前event中的dataContext对象即可获得当前选中的文件对象集合。

    private fun DataContext.getSelectedFiles(): Array<VirtualFile>? {return DataKeys.VIRTUAL_FILE_ARRAY.getData(this)//右键获取选中多个文件,扩展函数}
复制代码
  • 关键点三: Swing中JFileChooser组件的使用

关于JFileChooser组件的使用就比较简单了,这里就不去详细介绍,代码也很简单

  private void openFileAndSetPath(JComboBox<String> cBoxPath, int selectedMode, Boolean isSupportMultiSelect) {JFileChooser fileChooser = new JFileChooser();fileChooser.setFileSelectionMode(selectedMode);fileChooser.setMultiSelectionEnabled(isSupportMultiSelect);//设置文件扩展过滤器if (selectedMode != JFileChooser.DIRECTORIES_ONLY) {fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(".png", "png"));fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(".jpg", "jpg"));fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(".jpeg", "jpeg"));}fileChooser.showOpenDialog(null);if (selectedMode == JFileChooser.DIRECTORIES_ONLY) {//仅仅选择目录情况,不存在多文件选中File selectedDir = fileChooser.getSelectedFile();if (selectedDir != null) {cBoxPath.insertItemAt(selectedDir.getAbsolutePath(), 0);cBoxPath.setSelectedIndex(0);}} else {//选择含有文件情况,包括仅仅 选择文件 和 同时选择文件和目录,File[] selectedFiles = fileChooser.getSelectedFiles();if (selectedFiles != null && selectedFiles.length > 0) {cBoxPath.insertItemAt(getSelectedFilePath(selectedFiles), 0);cBoxPath.setSelectedIndex(0);}}}
复制代码
  • 关键点四: api key的验证和图片压缩的实现

在进行图片压缩前就是需要去验证一下TingPng ApiKey的合法性,如果第一次验证合法就需要把该ApiKey存储在本地,下次压缩就直接使用本地的key进行压缩,一旦本地key失效后,需要重新弹出TinyPng apikey 的验证提示框,进行重新认证。当然需要注意的是验证api key的合法性也是进行一次同步的网络请求所以它也要放在异步任务执行。

fun checkApiKeyValid(project: Project?,apiKey: String,validAction: (() -> Unit)? = null,invalidAction: ((String) -> Unit)? = null
) {if (apiKey.isBlank()) {invalidAction?.invoke("TinyPng key为空,请重新输入")}project?.asyncTask(hintText = "正在检查key是否合法", runAction = {try {Tinify.setKey(apiKey)Tinify.validate()} catch (exception: Exception) {throw exception}}, successAction = {validAction?.invoke()}, failAction = {println("验证Key失败!!${it.message}")invalidAction?.invoke("TinyPng key验证失败,请重新输入")})
}
复制代码

然后就是利用异步任务进行图片压缩操作。

fun slimImage(project: Project?,inputFiles: List<File>,model: ImageSlimmingModel = ImageSlimmingModel("", "", "", ""),successAction: (() -> Unit)? = null,outputSameFile: Boolean = false,failAction: ((String) -> Unit)? = null
) {project?.asyncTask(hintText = "正在压缩", runAction = {//执行图片压缩操作outputSameFile.yes {//针对右键选定图片情况,直接压缩当前目录选中图片,输出目录包括文件也是原来的inputFiles.forEach { inputFile -> Tinify.fromFile(inputFile.absolutePath).toFile(inputFile.absolutePath) }}.otherwise {inputFiles.forEach { inputFile -> Tinify.fromFile(inputFile.absolutePath).toFile(getDestFilePath(model, inputFile.name)) }}}, successAction = {successAction?.invoke()}, failAction = {failAction?.invoke("TinyPng key存在异常,请重新输入")})
}
复制代码

五、总结

到这里《用Kotlin撸一个图片压缩插件》系列文章就结束了,其实实现起来挺简单的,其中主要的关键点就是需要更加熟悉使用Intellij open api, 然后其他就是运用好Kotlin的一些语法特性,其余的都很简单。而且个人觉得把图片压缩做成一个插件会变得很高效,不然传统的模式得需要把图片拖到浏览器中然后一个一个下载下来,还有的人问我不就是一个脚本能解决的吗?脚本个人觉得不够灵活不能像插件一样任意在项目中选中一张或多张图片直接右键压缩。如有什么问题欢迎下方留言,谢谢。

插件项目源码地址

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450875.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

后端进阶技术总结

1、SDK与API的区别&#xff1f; SDK是Software Development Kit的缩写&#xff0c;即软件开发工具包。可以把SDK想象成一个虚拟的程序包&#xff0c;在这个程序包中有一份做好的软件功能&#xff0c;这份程序包几乎是全封闭的&#xff0c;通过接口联通外界&#xff0c;相应的接…

最简容器化动手小实践——再战flappybird

《Flappy Bird》是一名越南开发者所开发的游戏&#xff0c;这款游戏的主要内容是帮助一只小鸟穿越水管的层层阻碍&#xff0c;玩家所需要的只是点击屏幕从而调整小鸟的高度。而令这款游戏与众不同的是&#xff0c;这款游戏的难度夸张的惊人&#xff0c;大多数玩家在初次上手之后…

为什么电影电视帧率不取整数?

英文名称&#xff1a;time code时间码概念时间码&#xff08;time code&#xff09;是摄像机在记录图像信号的时候&#xff0c;针对每一幅图像记录的唯一的时间编码。一种应用于流的数字信号。该信号为视频中的每个帧都分配一个数字&#xff0c;用以表示小时、分钟、秒钟和帧数…

linux下用rpm 安装jdk

1.下载jdk的rpm安装包&#xff0c;这里以jdk-7u4-linux-i586.rpm为例进行说明 下载地址&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/index.html 2. 将jdk-7u4-linux-i586.rpm 移动到合适的安装目录上&#xff0c;安装软件不要在/home路径下&#xf…

FFT 入门

推荐博客 &#xff1a; https://oi.men.ci/fft-notes/ 卷积的理解 &#xff1a; https://www.zhihu.com/question/22298352?rf21686447 题目链接 &#xff1a;http://uoj.ac/problem/34 这是一道模板题。给你两个多项式&#xff0c;请输出乘起来后的多项式。输入格式第一行两个…

MPEG4视频压缩编码技术详解

MPEG全称是Moving Pictures Experts Group&#xff0c;它是“动态图象专家组”的英文缩写&#xff0c;该专家组成立于1988年&#xff0c;致力于运动图像及其伴音的压缩编码标准化工作&#xff0c;原先他们打算开发MPEG1、MPEG2、MPEG3和MPEG4四个版本&#xff0c;以适用于不同带…

oracle orion hugepages_settings.sh(支持OEL 7,4.1内核)

orion需要首先配置hugepage&#xff0c;否则会出现下列错误。[rootyyxxdb01 ~]# /opt/app/11.2.0/grid_home/bin/orion -run oltp -testname mytestORION: ORacle IO Numbers -- Version 11.2.0.4.0************************ Large Pages Information *******************Param…

eclipse启动出现“An Error has Occurred. See the log file”解决方法

见&#xff1a;http://blog.csdn.net/ww130929/article/details/52652222 这段时间开发java的项目&#xff0c;刚开始启动Eclipse的时候经常遇到这个问题&#xff0c;写这篇博客来记录解决方法。 1.删除工程目录下的&#xff1a; “.metadata/.plugins/org.eclipse.core.resour…

初识NIO之Java小Demo

Java中的IO、NIO、AIO&#xff1a; BIO&#xff1a;在Java1.4之前&#xff0c;我们建立网络连接均使用BIO&#xff0c;属于同步阻塞IO。默认情况下&#xff0c;当有一条请求接入就有一条线程专门接待。所以&#xff0c;在客户端向服务端请求时&#xff0c;会询问是否有空闲线程…

RTP协议详解

RTP协议分析 第1章. RTP概述 1.1. RTP是什么 RTP全名是Real-time Transport Protocol&#xff08;实时传输协议&#xff09;。它是IETF提出的一个标准&#xff0c;对应的RFC文档为RFC3550&#xff08;RFC1889为其过期版本&#xff09;。RFC3550不仅定义了RTP&#xff0…

线程状态转换

一、线程状态转换 新建&#xff08;New&#xff09; 创建后尚未启动。 可运行&#xff08;Runnable&#xff09; 可能正在运行&#xff0c;也可能正在等待 CPU 时间片。 包含了操作系统线程状态中的 Running 和 Ready。 阻塞&#xff08;Blocking&#xff09; 等待获取一个排它…

Eclipse中启动tomcat报错java.lang.OutOfMemoryError: PermGen space的解决方法

见&#xff1a;http://outofmemory.cn/java/OutOfMemoryError/outofmemoryerror-permgen-space-in-tomcat-with-eclipse 有的项目引用了太多的jar包&#xff0c;或者反射生成了太多的类&#xff0c;异或有太多的常量池&#xff0c;就有可能会报java.lang.OutOfMemoryError: Per…

MPEG-4 AVC/H.264 信息

作者&#xff1a;haibara 来源&#xff1a;pcicp.com 本FAQ由&#xff08;haibara&#xff09;翻译&#xff0c;期间受到kaito_mkid&#xff08;pcicp&#xff09;帮助&#xff0c;在此感谢&#xff0c;由于Newbie的关系&#xff0c;如有翻译错误&#xff0c;还请各位指出&…

eclipse搜索关键字

见&#xff1a;https://jingyan.baidu.com/article/e6c8503c1a60d2e54f1a18e3.html

装饰器语法糖运用

装饰器语法糖运用 前言&#xff1a;函数名是一个特性的变量&#xff0c;可以作为容器的元素&#xff0c;也可以作为函数的参数&#xff0c;也可以当做返回值。闭包定义&#xff1a; 内层函数对外层函数&#xff08;非全局&#xff09;变量的引用&#xff0c;这个内层函数就可以…

fb 4.7英文版 显示行数

窗口&#xff08;window&#xff09;首选项&#xff08;Preference&#xff09;—>常规&#xff08;General&#xff09;—>编辑器&#xff08;Editors&#xff09;—>文本编辑器&#xff08;Text Editors&#xff09;—>“显示行号”&#xff08;Show line number…

集市中迷失的一代:FreeBSD核心开发者反思开源软件质量

摘要&#xff1a;本文作者Poul-Henning Kamp (phkFreeBSD.org) &#xff0c;26年的计算机程序员&#xff0c;他编写的软件以底层构建块的形式广泛被开源和商业产品采用。讲述作者在看完《设计原本》这本书后所引发的共鸣&#xff01; 13年前&#xff0c;新兴的草根开源软件运动…

点击表格弹窗获取另外一套数据之后,原表格相关数据的调用

用H5新属性&#xff0c;data-*&#xff0c; $获取方式&#xff1a; 待续。。。。。。。 转载于:https://www.cnblogs.com/He-tao-yuan/p/9888316.html

谷歌浏览器如何如何禁用弹出窗口阻止程序

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 在工具栏上使用 Google Chrome 菜单。工具栏上的菜单位于浏览器右上角。 选择“设置”。 在页面底端找到并点击“显示高级设置”。 在“隐…

Python 3 入门,看这篇就够了

文章目录 简介基础语法运算符变量数据类型流程控制迭代器生成器函数 自定义函数参数传递 可更改与不可更改对象参数匿名函数变量作用域模块面向对象错误和异常文件操作序列化命名规范参考资料简介 Python 是一种高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。Pyt…