一、Gradle plugin是什么

Gradle是一个框架,他负责定义流程和规则,而具体的工作都是通过插件实现的。比如:编译Java的插件,编译Groovy的插件,编译Android APP的插件。Gradle 插件简单概括就是将构建逻辑的可重用部分打包起来,应用到不同的项目和构建中。gradle插件介入编译构建过程,以达到扩展功能目的。

二、Transform是什么

Gradle Transform是Android 官方提供给开发者在项目构建阶段(class->dex期间)用来修改.class文件的一套标准API。目前比较常用的就是,字节码插桩技术

Android的打包过程

gradle改镜像源 gradle plugins_自定义

通在红色箭头处通过Transform API拿到应用程序的class文件,对class文件中的方法进行遍历,然后找到我们需要改动的方法,修改目标方法,插入我们的代码保存,这就是字节码插桩技术。

Transform并不是必须的,只要找到class编译为dex的task之前的节点,通过before插入一个自定义task也可以实现,但使用Transform更简单。

三、自定义Gradle Plugin

3.1 自定义 Gradle 插件有三种方式:

1)构建脚本

可以直接在构建脚本中包含插件的源代码。缺点:插件只能在定义它的构建脚本之内可见,不能在其他脚本中复用插件。

2)buildSrc

Gradle会自动找到并编译buildSrc模块里面的插件,并使其在构建脚本的类路径中可用。 该插件对整个项目里的每个构建脚本都是可见的, 但是,它在项目外部不可见,因此不能其他项目中复用该插件。优点:方便调试。

3)独立项目

可以为插件创建一个单独的项目,将项目打包成一个JAR包,并通过然后可以在多个项目中复用

其中2)和3)可以使用创建一个uploadArchives task将插件上传到maven库或本地。

uploadArchives{    
repositories.mavenDeployer { 
 repository(url: uri("../repos")) // repos 为本地的地址,后续可以替换为网路上的 maven 库地址  
 pom.groupId = "com.example.plugin"       
 pom.artifactId = "AsmPlugin"       
 pom.version = "1.0.2"    }
}

3.2 自定义 Gradle 插件步骤

(1)新建一个 Android Library项目或者使用buildSrc

(2)main目录下建立groovy或者java目录和 resources目录,groovy或者java目录用于写插件逻辑, resources目录下用于声明自定义的插件;

(3)书写插件的方法就是,写一个类实现Plugin类,并实现其apply方法,在apply方法中完成插件逻辑;在自定义Plugin中可以注册一个自定义Tranform。

class AsmPlugin implements Plugin<Project> {    
@Override    public void apply(Project project) {        
System.out.println("AsmPlugin 执行")        
project.getExtensions().findByType(AppExtension.class).registerTransform(new DemoTransform(project))
}
}

注册之后,在编译流程中会通过TaskManager#createPostCompilationTasks为这个自定义的Transform生成一个对应的Task,(名字规则为:transformClasses+With+自定义transform名称+For+Debug/Release),在.class文件转换成.dex文件的流程中会执行这个Task,对所有的.class文件(可包括第三方库的.class)进行转换,转换的逻辑定义在Transform的transform方法中

(4)在resources目录下(建立/META-INF/gradle-plugins目录,并)建立一个(plugin.)properties的文件,在里面声明自定义的插件。这个properties文件的名称是我们应用插件时使用的名称

(5)在app的gradle文件里应用该插件

apply plugin: ‘com.example.xx-plugin’//插件工程里resources/META-INF/gradle-plugins 下的 properties 文件名称

四、自定义Transform

public class DemoTransform extends Transform {
    Project project;

    public DemoTransform(Project project) {
        this.project = project;
    }

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        
        //消费型输入,可以从中获取jar包和class文件夹路径。需要输出给下一个任务
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //引用型输入,无需输出。
        Collection<TransformInput> referencedInputs = transformInvocation.getReferencedInputs();

//OutputProvider管理输出路径
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();

        //先处理jar文件
        for (TransformInput input : inputs) {
            for(JarInput jarInput : input.getJarInputs()) {
                System.out.println("jar= " + jarInput.getName());
                File dest = outputProvider.getContentLocation(
                        jarInput.getFile().getAbsolutePath(),
                        jarInput.getContentTypes(),
                        jarInput.getScopes(),
                        Format.JAR);
                //to do some transform


                // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了
//                FileUtils.copyFile(jarInput.getFile(), dest);
            }

            //再处理class
            for(DirectoryInput directoryInput : input.getDirectoryInputs()) {
                if(directoryInput.getFile().isDirectory()){
                    for (File file : FileUtils.getAllFiles(directoryInput.getFile())) {
                        System.out.println("directoryInput--"+file.getName());
                    }
                }


                File dest = outputProvider.getContentLocation(
                        directoryInput.getName(),
                        directoryInput.getContentTypes(),
                        directoryInput.getScopes(),
                        Format.DIRECTORY);
                //建立文件夹
                FileUtils.mkdirs(dest);

                //to do some transform

                //将class文件及目录复制到dest路径
                FileUtils.copyDirectory(directoryInput.getFile(), dest);
            }

        }

    }

    //transform任务名字(用于尾部拼接)
    @Override
    public String getName() {
        return "DemoTransform";
    }

    //    Transform需要处理的类型
    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    //transform作用域,要处理所有class字节码,Scope我们一般使用TransformManager.SCOPE_FULL_PROJECT
    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    //增量编译开关,true只有增量编译时才回生效
    @Override
    public boolean isIncremental() {
        return true;
    }

在transform方法中,我们需要将每个jar包和class文件复制到dest路径,这个dest路径就是下一个Transform的输入数据。而在复制时,就可以将jar包和class文件的字节码做一些修改,再进行复制

4.1 支持增量编译

1)重写Transform的接口方法:isIncremental(),返回true。

2)判断当前编译对于Transform是否是增量编译:

如果不是增量编译,就按照前面的方式,依次处理所有的class文件;
(比如说clean之后的第一次编译没有增量基础,即使Transform的isIncremental放回true,当前编译对Transform仍然不是增量编译,所有需要依次处理所有的class文件)
如果是增量编译,根据每个文件的Status,处理文件:
(NOTCHANGED: 当前文件不需处理,甚至复制操作都不用;
ADDED、CHANGED: 正常处理,输出给下一个任务;
REMOVED: 移除outputProvider获取路径对应的文件。)

注意:当前编译对于Transform是否是增量编译受两个方面的影响:
(1)isIncremental() 方法的返回值;
(2)当前编译是否有增量基础;(clean之后的第一次编译没有增量基础,之后的编译有增量基础)

增量的时间缩短为全量的速度提升了3倍多,而且这个速度优化会随着工程的变大而更加显著

五、ASM是什么

ASM是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。通俗点讲就是来处理javac编译之后的class文件

ASM的原理如下图所示:

gradle改镜像源 gradle plugins_自定义_02

• ClassReader:用于读取已经编译好的.class文件。

• ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。

• 各种Visitor类:如上所述,CoreAPI根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor,比如用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor

六、ASM怎么使用

ASM由于是基于jvm指令集的比较晦涩难懂,可以使用ASM Bytecode Outline插件辅助开发。

使用这个插件可以帮助我们查看字节码,并直接生成ASM代码。我们可以先把要修改的代码补充到对应的类里面,然后通过code->Asm Bytecode Viewer运行,即可得到对应的ASMified代码。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。