You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
android-notes/blogs/Android/Gradle/TinyPngPlugin.md

7.9 KiB

Gralde Plugin 实践之 TinyPng Plugin

前言

在上一篇文章中,我们熟悉了如何去实现一个自定义的 Gradle Plugin,本来按照计划这篇文章是讲 Transform API,但是考虑到学完新知识最好能实践一下,之前也讲到可以利用 TinyPng 在构建项目的时候批量压缩 res 下的所有 png 图片,今天我们就来实践一下,这个并不涉及到 Transform API 的使用,但是需要熟悉 Groovy 一些常见的操作,比如 Extensions、 Json 的解析和生成等,整个项目很简单,代码并不多,大胆 fork TinyPngPlugin 吧。

实现方式

我们可以直接利用 TinyPng 给的 API 即可,文档地址为:

https://tinypng.com/developers/reference/java

核心源码如下:

Tinify.setKey("YOUR_API_KEY");
Tinify.fromFile("unoptimized.png").toFile("optimized.png");

也就是说,我们只需要配置 API_KEY 就好了,文件的输入路径和输出路径肯定就是 res 文件夹下的 drawable 或 mipmap 了。不过我们肯定想知道,压缩前后的图片的大小差距,这里可以通过生成 Json 文件来查看。

需要注意的是,如果你想把这个 Plugin 上传到仓库给别人使用,那肯定不能在工程里面写死 API_KEY,这个很好理解,也就是说我们需要用户可以动态配置 API_KEY。这就要牵扯到 Extensions 了。

Extensions

Extensions 即自定义配置项,这是什么呢?我们可以参考 app 模块的 build.gradle 文件:

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "top.omooo.pluginproject"
        minSdkVersion 23
        targetSdkVersion 28
        //...
    }
   	//...
}

如何把 compileSdkVersion 和 applicationId 的值解析出来呢?很简单,我们可以直接在 app 模块的 build.gradle 文件添加以下代码(即第一篇文章的第一种方式):

class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        target.task("testExtensions") << {
            def android = target['android']
            println(android.compileSdkVersion)
            def defaultConfig = android['defaultConfig']
            println(defaultConfig.applicationId)
        }
    }
}

apply plugin: MyPlugin

然后执行 Task 就可以看到输出了。

熟悉了以上操作,这时候我们就可以确定我们的自定义配置项为以下结构:

tinyInfo {
    //资源目录
    resourceDir = [
            "app/src/main/res",
            "other_module/src/main/res"
    ]
    resourcePattern = [
            "drawable[a-z-]*",
            "mipmap[a-z-]*"
    ]
    whiteList = [

    ]
    apiKey = "******"
}

我们可以为以上的结构写一个 Bean 类,命名为 TinyPngExtension.groovy:

class TinyPngExtension {

    ArrayList<String> resourceDir
    ArrayList<String> resourcePattern
    ArrayList<String> whiteList
    String apiKey

    TinyPngExtension() {
        resourceDir = []
        resourcePattern = []
        whiteList = []
        apiKey = null
    }


    @Override
    public String toString() {
        return "TinyPngExtension.groovy{" +
                "resourceDir=" + resourceDir +
                ", resourcePattern=" + resourcePattern +
                ", whiteList=" + whiteList +
                ", apiKey='" + apiKey + '\'' +
                '}'
    }
}

需要注意的是,这里的变量名一定要和配置项的字段名一致

这个 Plugin 的大部分代码都在 Task,所以我们把这个 Task ( TinyPngTask.groovy ) 抽出来:

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class TinyPngTask extends DefaultTask {
    TinyPngExtension mTinyPngExtension

    TinyPngTask() {
        mTinyPngExtension = project.tinyInfo
    }

    @TaskAction
    void run() {
        println("Task run~")
        println(mTinyPngExtension.toString())
    }
}

注意,这里我们用了一个 @TaskAction 的注解,这个注解表示该方法表示为 Task 的执行实体,方法名随意写都行。

然后我们在 Plugin 里去执行这个 Task ( CrazyPlugin.groovy ):

class CrazyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {

        project.extensions.create("tinyInfo", TinyPngExtension)

        project.afterEvaluate {
            project.task("tinyTask", type: TinyPngTask)
        }
    }
}

回顾上一篇文章,我们说到,既然我们修改了 Plugin 的代码,就要重新生成 jar 包上传依赖,即执行:

./gradlew task uploadArchives

然后在 app 模块的 build.gradle 文件里,我们就可以 apply Plugin 并且添加 Extensions 了:

apply plugin: 'top.omooo.crazy_plugin'

tinyInfo {
    //资源目录
    resourceDir = [
            "app/src/main/res",
            "other_module/src/main/res"
    ]
    resourcePattern = [
            "drawable[a-z-]*",
            "mipmap[a-z-]*"
    ]
    whiteList = [

    ]
    apiKey = "******"
}

然后我们在执行:

./gradlew task tinyTask

就可以输出我们的自定义配置项了。也就是说,我们写的自定义配置项可以在 Task 中获取的。

Json 解析与生成

其实本来我是不想讲这个的,主要是因为太简单了,但是不讲这个好像没什么可写的了 :)

class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        target.task("testJson") << {

            def jsonFile = new File("${project.projectDir}/test.json")
            if (!jsonFile.exists()) {
                jsonFile.createNewFile()
            }
            //把 List 写入 json 文件中
            ArrayList<String> list = ["Omooo", "Tom", "Test"]
            def jsonOutput = new JsonOutput()
            def json = jsonOutput.toJson(list)
            jsonFile.write(jsonOutput.prettyPrint(json), "utf-8")

            //把 json 文件读成 List
            def readList = new JsonSlurper().parse(jsonFile, "utf-8")
            println("${readList.toString()}")
        }
    }
}

apply plugin: MyPlugin

完全不用我过多解释了,大家都能看懂。但是这个有什么用嘛?

我们会通过配置项中拿到资源目录,然后遍历资源目录,批量压缩目录下的所有 png 图片,这时候我想把压缩前后的图片大小记录下来,就要用到写 Json 文件;下次压缩的时候,之前压缩过的图片,肯定不希望再次压缩了,所以就需要用到读 Json 文件转成 List,在这 List 的里面的图片就不用压缩了。

结果

这是压缩后的结果 Json 文件:

[
    {
        "preSize": "6.73KB",
        "md5": "cebfdfd96b51a2bbde7cfa40b7ea2997",
        "postSize": "3.48KB",
        "path": "app/src/main/res/mipmap-xhdpi/ic_launcher_round.png"
    },
		//...
    {
        "preSize": "74.46KB",
        "md5": "d506a51ab02f8e527b2d5b13b24ba9f7",
        "postSize": "16.30KB",
        "path": "app/src/main/res/drawable/thread_lifecycle.png"
    }
]
//*************//
Task finish,compress 11 files
Before total size: 141400
After total size: 49327
//*************//

效果还是很明显的~

代码

TinyPngPlugin

本文无限感谢 https://github.com/waynell/TinyPngPlugin

其实代码并没有太大差别,毕竟代码就那些。

还记得自己当初 fork 这个库的时候,对于自定义 Plugin 流程不熟悉,还有就是不熟悉 Groovy 语法等一系列原因,一直没有去自己写一遍。当你熟悉了自定义 Plugin 流程后,Groovy 语法并不多,主要的我都讲到了,其实整个流程还是很简单的,还是开头那句话:

放心大胆的去 Fork 吧~