Android 自定义Gradle插件基础

Gradle 定义

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划未来将支持更多的语言。
AndroidStudio现在的构建工具大部分是采用的Gradle,今天我们尝试编写一个Gradle插件,利用lint自动删除无用资源。
实现思路:先执行lint任务,通过解析生成的xml文件,找到id为UnusedResources的文件路径,并遍历删除,输出日志。
废话不多说了,嘀嘀嘀老司机开车啦!

项目地址:gradle-lint-plugin

创建module

新建一个工程,再新建一个Module作为插件模块,删除里面所有文件,新建src/main/groovy文件夹,留下build.gradle,目录如下


因为是基于groovy开发,所有代码文件要以.groovy结尾

配置build.gradle

加入该插件依赖的库,设置group和version,使用maven仓库,这里配置了上传到本地文件夹

接下来看一下这三个类CleanResPlugin、CleanTask、PluginExtension是做什么的
创建插件类CleanResPlugin.groovy,由于Android Studio不能直接创建.groovy为后缀的文件,所以需要先创建一个java文件,然后修改其后缀
package com.zxy.gradle;
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task

public class CleanResPlugin implements Plugin<Project> {
static final String GROUP = 'LintCleaner'
static final String EXTENSION_NAME = 'lintCleaner'

@Override
void apply(Project project) {
    // 获取外部参数
    project.extensions.create(EXTENSION_NAME, PluginExtension, project)

    // 创建清理任务
    Task cleanTask = project.tasks.create(CleanTask.NAME, CleanTask)

    // 执行完lint后,再执行
    cleanTask.dependsOn project.tasks.getByName('lint')

}
}

上面定义了插件入口

CleanTask.groovy
package com.zxy.gradle;

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

public class CleanTask extends DefaultTask {

// 任务名
static final String NAME = "cleanUnusedRes"
final String UNUSED_RESOURCES_ID = "UnusedResources"
final String ISSUE_XML_TAG = "issue"
HashSet<String> mFilePaths = new HashSet<>()
StringBuilder mDelLogSb = new StringBuilder()
StringBuilder mKeepLogSb = new StringBuilder()

public CleanTask() {
    group = CleanResPlugin.GROUP
    description = "Removes unused resources reported by Android lint task"
}

@TaskAction
def start() {
    def ext = project.extensions.findByName(CleanResPlugin.EXTENSION_NAME) as PluginExtension
    println  ext.toString()

    def file = new File(ext.lintXmlPath)
    if (!file.exists()) {
        println '找不到lint的xml文件,请检查路径是否正确! '
        return
    }

    // 解析xml,添加无用文件的路径到容器中
    new XmlSlurper().parse(file).'**'.findAll { node ->
        if (node.name() == ISSUE_XML_TAG && node.@id == UNUSED_RESOURCES_ID) {
            mFilePaths.add(node.location.@file)
        }
    }

    def num = mFilePaths.size()
    if (num > 0) {
        mDelLogSb.append("num:${num}\n")
        mDelLogSb.append("\n=====删除的文件=====\n")
        mKeepLogSb.append("\n=====保留的文件=====\n")
        for (String path : mFilePaths) {
            println path
            deleteFileByPath(path)
        }
        writeToOutput(ext.outputPath)
    } else {
        println '不存在无用资源!'
    }
}

def deleteFileByPath(String path) {
    if (isDelFile(path)) {
        if (new File(path).delete()){
            mDelLogSb.append('\n\t' + path)

        } else {
            mKeepLogSb.append('\n\t删除失败:' + path)

        }
    } else {
        mKeepLogSb.append('\n\t' + path)

    }
}

/**
 * 只选定drawable,mipmap,menu下的文件,(无用引用暂不处理)
 * @param path
 */
def isDelFile(String path) {
    String dir = path
    (dir.contains('layout')||dir.contains('drawable') || dir.contains('mipmap') || dir.contains('menu')) && (dir.endsWith('.png') || dir.endsWith('.jpg') || dir.endsWith('.jpeg'))
}

def writeToOutput(def path) {
    def f = new File(path)
    if (f.exists()) {
        f.delete()
    }
    new File(path).withPrintWriter { pw ->
        pw.write(mDelLogSb.toString())
        pw.write(mKeepLogSb.toString())
    }
}

}

必须要继承DefautTask,并使用@TaskAction来定义Task的入口函数,通过lint的xml文件进行解析,找到无用文件的路径,进行删除文件操作。

PluginExtension.groovy
package com.zxy.gradle;

import org.gradle.api.Project

public class PluginExtension {
String lintXmlPath
String outputPath

public PluginExtension(Project project) {
    // 默认路径   
    lintXmlPath = "$project.buildDir/reports/lint-results.xml"
    outputPath = "$project.buildDir/reports/lintCleanerLog.txt"
}

@Override
String toString() {
    return "配置项:\n\tlintXmlPath:" + lintXmlPath + "\n" +
            "outputPath:" + outputPath + "\n"
}

}

配置文件读写目录

定义插件id
implementation-class=com.zxy.gradle.CleanResPlugin

main文件夹下新建resources/META-INF/gradle-plugins目录,再新建com.zxy.cleaner.properties文件,这里com.zxy.cleaner作为id,应用到project时要使用。里面的内容指向插件入口

编译并上传到本地


下面是输出结果,可以在上面配置的目录下找到,记住是执行两次task,才会成功上传到本地

下午2:27:00: Executing task 'uploadArchives'...

Executing tasks: [uploadArchives]

Configuration on demand is an incubating feature.
project.buildDir-------------/Users/xinyu/work/mobi/MyPlugin/app/build
:app:uploadArchives
:plugin:compileJava NO-SOURCE
:plugin:compileGroovy
:plugin:processResources UP-TO-DATE
:plugin:classes
:plugin:jar
:plugin:uploadArchives

BUILD SUCCESSFUL in 1s
5 actionable tasks: 4 executed, 1 up-to-date
下午2:27:02: Task execution finished 'uploadArchives'.

在项目的build.gradle里面配置插件

buildscript {

repositories {
    google()
    jcenter()
    maven {
        url 'file:///Users/xinyu/work/maven-lib/'

    }
}
dependencies {
    classpath 'com.android.tools.build:gradle:3.1.2'
    classpath 'com.zxy.plugin:plugin:1.0.0'

    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}
}

在app.gradle里面配置

apply plugin: 'com.android.application'
apply plugin: 'com.zxy.cleaner'


lintCleaner {
lintXmlPath "${buildDir}/reports/lint-results.xml"
outputPath "${buildDir}/reports/lintCleanerLog.txt"
}

测试

加入几张无用的资源,命令行执行 ./gradlew cleanUnusedRes 或者在右侧Gradle的Tasks中找到并双击执行,输出log

num:2

=====删除的文件=====

/Users/xinyu/work/mobi/MyPlugin/app/src/main/res/drawable/test_res.png
/Users/xinyu/work/mobi/MyPlugin/app/src/main/res/layout/test_layout.xml
=====保留的文件=====

我们发现删除了无用的资源!插件开发完成~

总结

我们初步完成了自己定义一个gradle插件的任务,如果直接用到项目里还有一些问题,比如测试Task里面的删除过滤的规则要做一定的修改,否则会把一些有用的资源删掉

Gradle项目构框架使用groovy语言实现。基于Gradle框架为我们实现了一些项目构件框架,要开发Android gradle插件开发需要对groovy语法、gradle框架、Android打包流程有一定的熟悉,这为我们打开了一扇门,如果有兴趣可以继续研究Groovy,它还可以做很多事情


点赞加关注是给我最大的鼓励!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容

  • 背景 做为一个Android的开发者,我相信现在几乎所有的Android开发都是利用Android Studio作...
    Only凹凸曼阅读 4,034评论 1 10
  • 这一章主要针对项目中可以用到的一些实用功能来介绍Android Gradle,比如如何隐藏我们的证书文件,降低风险...
    acc8226阅读 7,610评论 3 25
  • 这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在...
    呆萌狗和求疵喵阅读 15,985评论 22 80
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 20岁之前,我没谈过恋爱, 无关乎年龄和学业,只是喜欢我的人我不喜欢,我喜欢的不喜欢我,现在想想我一直对爱情都有着...
    我是小公主呀阅读 233评论 1 0