手把手教你实现一个 Gradle Tansform 实例

关于Gradle Transform API 的详细分析我之前有一篇文章Android Gradle Transform 详解已经讲到了,这里不再重复,直接开始上手撸代码,还没看过前面一篇博客的同学建议先去看看,好帮助理解。

1、 新建 Android Library Module :plugin,清空plugin的build.gradle文件中的内容,然后修改成如下内容

apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
    implementation gradleApi() //gradle sdk
    implementation localGroovy() //groovy sdk

    implementation 'com.android.tools.build:gradle:3.4.1'
}
repositories {
    jcenter()
}

uploadArchives {
    repositories.mavenDeployer {
        //本地仓库路径,以放到项目根目录下的 repo 的文件夹为例
        repository(url: uri('../repo'))

        //groupId ,自行定义,组织名或公司名
        pom.groupId = 'com.jokerwan'

        //artifactId,自行定义,项目名或模块名
        pom.artifactId = 'autotrack.android'

        //插件版本号
        pom.version = '1.0.0'
    }
}

我们这里是发布plugin插件到本地仓库,上面配置的groupIdartifactIdversion属性能容都是可以自定义的,当应用程序引用这个插件时会用到这些信息。通过repositories属性,可以把uri配置在本地目录,这样就可以把maven设置成本地仓库,我们目前把仓库配置到当前Project根目录下的repo目录。

2、 删除plugin/src/main目录下的所有文件,新建groovy目录

因为插件我们用的是groovy语言开发的,所以需要放到groovy目录下。接着再groovy目录下新建一个package com.jokerwan.demo.plugin来存放Transform类文件

3、 创建Transform类

在包com.jokerwan.demo.plugin下创建JokerWanTransform.groovy类,直接new一个file,名称为“JokerWanTransform.groovy”,代码如下:

package com.jokerwan.demo.plugin

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Project

class JokerWanTransform extends Transform {

    private static Project project
    private static final String NAME = "JokerWanAutoTrack"

    JokerWanTransform(Project project) {
        this.project = project
    }

    @Override
    String getName() {
        return NAME
    }

    /**
     * 需要处理的数据类型,有两种枚举类型
     * CLASSES 代表处理的 java 的 class 文件,RESOURCES 代表要处理 java 的资源
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * 指 Transform 要操作内容的范围,官方文档 Scope 有 7 种类型:
     * 1. EXTERNAL_LIBRARIES        只有外部库
     * 2. PROJECT                   只有项目内容
     * 3. PROJECT_LOCAL_DEPS        只有项目的本地依赖(本地jar)
     * 4. PROVIDED_ONLY             只提供本地或远程依赖项
     * 5. SUB_PROJECTS              只有子项目。
     * 6. SUB_PROJECTS_LOCAL_DEPS   只有子项目的本地依赖项(本地jar)。
     * 7. TESTED_CODE               由当前变量(包括依赖项)测试的代码
     * @return
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    static void printCopyRight() {
        println()
        println("******************************************************************************")
        println("******                                                                  ******")
        println("******                欢迎使用 JokerWanTransform 编译插件               ******")
        println("******                                                                  ******")
        println("******************************************************************************")
        println()
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        printCopyRight()

        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()

        // Transform 的 inputs 有两种类型,一种是目录,一种是 jar 包,要分开遍历
        transformInvocation.inputs.each { TransformInput input ->
            input.jarInputs.each { JarInput jarInput ->
                // 处理jar
                processJarInput(jarInput, outputProvider)
            }

            input.directoryInputs.each { DirectoryInput directoryInput ->
                // 处理源码文件
                processDirectoryInput(directoryInput, outputProvider)
            }
        }

    }

    void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
        // 重命名输出文件(同目录copyFile会冲突)
        def jarName = jarInput.name
        def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
        if (jarName.endsWith(".jar")) {
            jarName = jarName.substring(0, jarName.length() - 4)
        }

        File dest = outputProvider.getContentLocation(
                jarName + md5Name,
                jarInput.getContentTypes(),
                jarInput.getScopes(),
                Format.JAR
        )

        // TODO do some transform

        // 将 input 的目录复制到 output 指定目录
        FileUtils.copyFile(jarInput.getFile(), dest)
    }

    void processDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        File dest = outputProvider.getContentLocation(
                directoryInput.getName(),
                directoryInput.getContentTypes(),
                directoryInput.getScopes(),
                Format.DIRECTORY
        )

        // TODO do some transform

        // 将 input 的目录复制到 output 指定目录
        FileUtils.copyDirectory(directoryInput.getFile(), dest)
    }
}

我们在transform方法里面,先打印一个提示信息,然后分别遍历目录和jar包,在这里我们仅仅把所有的输入文件拷贝到目标目录下,并没有对文件进行任何处理。这里需要注意的是,即使我们对文件没有任何处理,任然需要将所有的输入文件拷贝到目标目录下,否则下一个Task就没有TansformInput了,如果我们将 input 的目录复制到 output 指定目录,最后会导致打包的apk缺少.class文件

4、 创建plugin

在包com.jokerwan.demo.plugin下创建JokerWanPlugin.groovy类,并将JokerWanTransform类注册进去

class JokerWanPlugin implements Plugin<Project> {
    void apply(Project project) {
        AppExtension appExtension = project.extensions.findByType(AppExtension.class)
        appExtension.registerTransform(new JokerWanTransform(project))
    }
}

JokerWanPlugin实现了Plugin<Project>接口中的apply(Project project)方法,获取一个appExtension对象,然后调用其registerTransform方法将JokerWanTransformNew的实例注册进去

5、 创建properties文件

在plugin/src/main目录下新建目录 resources/META-INF/gradle-plugins,接着在此目录下新建文件com.jokerwan.android.properties,文件内容如下:

implementation-class=com.jokerwan.demo.plugin.JokerWanPlugin

文件名com.jokerwan.android就是用来指定插件名称的,apply该组件时会用到,即

apply plugin: 'com.jokerwan.android'

“=”号后面的内容就是我们插件类JokerWanPlugin的全类名

6、 构建plugin

执行plugin的uploadArchives任务构建plugin

构建成功之后,在项目的根目录会生成一个repo目录,里面存放的就是plugin插件的目标文件。构建成功输出信息如下:


7、 添加对插件的依赖

7.1、 修改项目根目录下的build.gradle文件
7.2、 修改app/build.gradle文件

8、 构建应用程序

可以通过命令行进行编译

./gradlew assembleDebug

如果编译没有报错的话并且看到相应的输出信息,就可以说明上面定义的JokerWanTransform已经成功运行了。编译成功输出信息如下:

demo代码如下
https://github.com/isJoker/TestTransform

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