Gradle学习系列(五):Gradle Transform

Gradle学习系列(五):Gradle Transform,第1张

概述概述又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记Gradle系列(一):Groovy学习Gradle学习系列(二):Gradle核心解密Gradle学习系列(三):Gradle插件Gradle学习系列(四):Gradle依赖Gradle学习系列(五):GradleTransform简介Google 概述

又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记

Gradle系列(一):Groovy学习

Gradle学习系列(二):Gradle核心解密

Gradle学习系列(三):Gradle插件

Gradle学习系列(四):Gradle依赖

Gradle学习系列(五):Gradle Transform

简介

Google从 AndroID Gradle 1.5.0开始提供了transform API,Gradle transform是AndroID 官方提供给开发者在项目构建阶段(class->dex期间)用来修改.class文件的一套标准API,把class文件转换为字节码,然后 *** 作字节码,目前比较常用的就是,字节码插桩技术

AndroID的打包过程

通过上图我们可以知道,我们需要在红色箭头处通过transform API拿到应用程序的class文件,然后借助AMS之类的库,对class文件中的方法进行遍历,然后找到我们需要改动的方法,修改目标方法,插入我们的代码保存,这就是字节码插桩技术

transform相关方法介绍

实现一个transform需要创建一个Gradle插件,至于插件相关可以看我之前的文章Gradle学习系列(三):Gradle插件
,这里就直接从transform开始讲了

class Renxhtransform extends transform {    @OverrIDe    String getname() {        return null    }    @OverrIDe    Set<QualifIEdContent.ContentType> getinputTypes() {        return null    }    @OverrIDe    Set<? super QualifIEdContent.Scope> getScopes() {        return null    }    @OverrIDe    boolean isIncremental() {        return false    }    @OverrIDe    voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException {        super.transform(transformInvocation)    }}

可以看到如果实现一个transform主要有四个方法,下面依次讲一下这四个方法

getname

返回transform的名称,一个应用内可以有多个transform,因此需要一个名称进行标识,方便后面调用

那么最终的名字是怎么组成的呢?

在gradle plugin 的源码中有一个transformManager的类,他的主要作用就是管理所有的transform子类,里面一个方法getTasknamePrefix就是名字的规则

 static String getTasknamePrefix(@NonNull transform transform) {        StringBuilder sb = new StringBuilder(100);        sb.append("transform");        sb.append(                transform                        .getinputTypes()                        .stream()                        .map(                                inputType ->                                        CaseFormat.UPPER_UNDERscore.to(                                                CaseFormat.UPPER_CAMEL, inputType.name()))                        .sorted() // Keep the order stable.                        .collect(Collectors.joining("And")));        sb.append("With");        StringHelper.appendCAPItalized(sb, transform.getname());        sb.append("For");        return sb.toString();    }

名字是以transform开头,然后拼接ContentType这个下面详细讲,ContentType之间用add拼接,然后加上with最后拼接getname返回的name

getinputTypes

获取输入类型,ContentType表示类型,我们看下源码

    enum DefaultContentType implements ContentType {        /**         * The content is compiled Java code. This can be in a Jar file or in a folder. If         * in a folder, it is expected to in sub-folders matching package names.         */        CLASSES(0x01),        /** The content is standard Java resources. */        RESOURCES(0x02);        private final int value;        DefaultContentType(int value) {            this.value = value;        }        @OverrIDe        public int getValue() {            return value;        }    }

这里表示输入类型包括俩种,CLASSES 和 RESOURCES分别代表java的class文件和资源文件

getScopes

这个是指transform需要处理那些输入文件,官方文档一共有7中范围

EXTERNAL_liBRARIES : 只有外部库PROJECT : 只有项目内容PROJECT_LOCAL_DEPS : 只有项目的本地依赖(本地jar)PROVIDED_ONLY : 只提供本地或远程依赖项SUB_PROJECTS : 只有子项目SUB_PROJECTS_LOCAL_DEPS: 只有子项目的本地依赖项(本地jar)TESTED_CODE :由当前变量(包括依赖项)测试的代码

看下源码

 enum Scope implements ScopeType {        /** Only the project (module) content */        PROJECT(0x01),        /** Only the sub-projects (other modules) */        SUB_PROJECTS(0x04),        /** Only the external librarIEs */        EXTERNAL_liBRARIES(0x10),        /** Code that is being tested by the current variant, including dependencIEs */        TESTED_CODE(0x20),        /** Local or remote dependencIEs that are provIDed-only */        PROVIDED_ONLY(0x40),        /**         * Only the project's local dependencIEs (local jars)         *         * @deprecated local dependencIEs are Now processed as {@link #EXTERNAL_liBRARIES}         */        @Deprecated        PROJECT_LOCAL_DEPS(0x02),        /**         * Only the sub-projects's local dependencIEs (local jars).         *         * @deprecated local dependencIEs are Now processed as {@link #EXTERNAL_liBRARIES}         */        @Deprecated        SUB_PROJECTS_LOCAL_DEPS(0x08);        private final int value;        Scope(int value) {            this.value = value;        }        @OverrIDe        public int getValue() {            return value;        }    }

如果范围越小,我们需要处理文件就越少,处理的速度就会越快

isIncremental

表示是否支持增量编译,一个自定义的transform在可能的情况下,支持增量编译,可以节省一些编译的时间和资源

transform

transform方法的参数transformInvocation是一个接口,提供一些关于输入的基本信息,利用这些接口就可以获得编译流程中的class文件进行 *** 作

从上图可以看到,其实整体的处理流程还是很简单的,就是从transformInvocation获取输入,然后按照class文件夹和jar集合进行遍历,拿到所有的class文件,进行处理

  @OverrIDe    voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException {        super.transform(transformInvocation)        printcopyRight()        transformOutputProvIDer transformOutputProvIDer = transformInvocation.getoutputProvIDer()        List<transforminput> inputs= transformInvocation.getinputs()        transformInvocation.getinputs().each { transforminput transforminput ->            transforminput.jarinputs.each { Jarinput jarinput ->                println("jar=" + jarinput.name)            }            transforminput.directoryinputs.each { Directoryinput directoryinput ->                directoryinput.getfile().eachfile { file file ->                    printfile(file)                }            }        }    }
transforminput

transforminput指的是输入文件的一个抽象,包括

Directoryinput集合
指源码方式参与项目编译的所有目录结构以及其中的源码文件Jarinput集合

是指以jar包的形式参与项目编译的所有的本地jar包和远程jar包(包括aar)

transformOutputProvIDer

指的是transform的输出,通过他可以获取输出路径信息

实战第一步

首先我们要创建一个插件项目,至于插件相关可以看我之前的文章Gradle学习系列(三):Gradle插件

与上次相比,这次多了引入了 implementation 'com.androID.tools.build:gradle:3.4.1'因为这次需要用到androID plugin中的API

第二步

创建文件Renxhtransform.groovy继承transform

package com.renxh.cuspluginimport com.androID.build.API.transform.Directoryinputimport com.androID.build.API.transform.Formatimport com.androID.build.API.transform.Jarinputimport com.androID.build.API.transform.QualifIEdContentimport com.androID.build.API.transform.transformimport com.androID.build.API.transform.transformExceptionimport com.androID.build.API.transform.transforminputimport com.androID.build.API.transform.transformInvocationimport com.androID.build.API.transform.transformOutputProvIDerimport com.androID.build.gradle.internal.pipeline.transformManagerimport com.androID.utils.fileUtilsimport org.gradle.API.Projectclass Renxhtransform extends transform {    Project mProject    Renxhtransform(Project project) {        this.mProject = project;    }    @OverrIDe    String getname() {        return "Renxhtransform"    }    /**     * 需要处理的数据类型,有两种枚举类型     * 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    }    @OverrIDe    voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException {        super.transform(transformInvocation)        printcopyRight()        transformOutputProvIDer transformOutputProvIDer = transformInvocation.getoutputProvIDer()        transformInvocation.getinputs().each { transforminput transforminput ->            transforminput.jarinputs.each { Jarinput jarinput ->                println("jar=" + jarinput.name)                processJarinput(jarinput, transformOutputProvIDer)            }            transforminput.directoryinputs.each { Directoryinput directoryinput ->                if (directoryinput.file.isDirectory()) {                    fileUtils.getAllfiles(directoryinput.file).each { file file ->                        println(file.name)                    }                }                processDirectoryinputs(directoryinput, transformOutputProvIDer)            }        }    }    static voID printfile(file file) {        if (file.isDirectory()) {            file[] files = file.Listfiles()            files.each { file file1 ->                if (file1.isDirectory()) {                    printfile(file1)                } else {                    println("file = " + file.name)                }            }        } else {            println("file = " + file.name)        }    }    static voID printcopyRight() {        println()        println("******************************************************************************")        println("******                                                                  ******")        println("******                欢迎使用 Renxhtransform 编译插件                    ******")        println("******                                                                  ******")        println("******************************************************************************")        println()    }    static voID processJarinput(Jarinput jarinput, transformOutputProvIDer outputProvIDer) {        file dest = outputProvIDer.getContentLocation(                jarinput.getfile().getabsolutePath(),                jarinput.getContentTypes(),                jarinput.getScopes(),                Format.JAR)        // to do some transform        // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了        fileUtils.copyfile(jarinput.getfile(),dest)    }    static voID processDirectoryinputs(Directoryinput directoryinput, transformOutputProvIDer outputProvIDer) {        file dest = outputProvIDer.getContentLocation(directoryinput.getname(),                directoryinput.getContentTypes(), directoryinput.getScopes(),                Format.DIRECTORY)        // 建立文件夹        fileUtils.mkdirs(dest)        // to do some transform        // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了        fileUtils.copyDirectory(directoryinput.getfile(),dest)    }}

可以看到其实最主要的代码在transform()方法中,上面主要是打印了我们自定义的文本,然后分别遍历directory和jar包然后打印出他们的名字,这里没有对文件进行处理,最后把输入文件拷贝到目标目录下,需要注意的是即使我们没有对文件做任何处理,我们仍然需要把输入文件拷贝到目标目录下,否则下一个Task就没有Tansforminput,如果我们将input目录复制到output指定目录,会导致最后的打包的apk缺少class

第三步

在插件中注册自定的transform

class CustomPlugin implements Plugin<Project> {    @OverrIDe    voID apply(Project project) {        AppExtension appExtension = project.getExtensions().findByType(AppExtension.class)        appExtension.registertransform(new Renxhtransform(project))                }

app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.androID.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.androID.library’,我们看下Gradle的源码com.androID.application插件对应的实现类是AppPlugin

AppPlugin中添加Extension就是AppExtension,源码如下,主要看createExtension

public class AppPlugin extends BasePlugin implements Plugin<Project> {    @Inject    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {        super(instantiator, registry);    }    protected BaseExtension createExtension(Project project, Projectoptions projectoptions, Instantiator instantiator, AndroIDBuilder androIDBuilder, SdkHandler sdkHandler, namedDomainObjectContainer<BuildType> buildTypeContainer, namedDomainObjectContainer<ProductFlavor> productFlavorContainer, namedDomainObjectContainer<SigningConfig> signingConfigContainer, namedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelinfo extraModelinfo) {        return (BaseExtension)project.getExtensions().create("androID", AppExtension.class, new Object[]{project, projectoptions, instantiator, androIDBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelinfo});    }    public voID apply(Project project) {        super.apply(project);    }    //省略...}

这里添加了androIDExtension实现类是AppExtension,我们的transform最后添加到了AppExtension的基类BaseExtension的容器中了

看下效果

执行 终端执行./gradlew app:assemble 命令

已经达到想要的效果

参考

Android Gradle Transform 详解

Gradle Transform + ASM 探索

总结

以上是内存溢出为你收集整理的Gradle学习系列(五):Gradle Transform全部内容,希望文章能够帮你解决Gradle学习系列(五):Gradle Transform所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1024586.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存