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


點贊加關注是給我最大的鼓勵!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章