Android 自定义Gradle插件基础

作者: Code猎人 | 来源:发表于2018-06-04 14:49 被阅读53次

    Gradle 定义

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

    创建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,它还可以做很多事情


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

    相关文章

      网友评论

        本文标题:Android 自定义Gradle插件基础

        本文链接:https://www.haomeiwen.com/subject/snmesftx.html