一、Gradle plugin是什么
Gradle是一个框架,他负责定义流程和规则,而具体的工作都是通过插件实现的。比如:编译Java的插件,编译Groovy的插件,编译Android APP的插件。Gradle 插件简单概括就是将构建逻辑的可重用部分打包起来,应用到不同的项目和构建中。gradle插件介入编译构建过程,以达到扩展功能目的。
二、Transform是什么
Gradle Transform是Android 官方提供给开发者在项目构建阶段(class->dex期间)用来修改.class文件的一套标准API。目前比较常用的就是,字节码插桩技术
Android的打包过程
通在红色箭头处通过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的原理如下图所示:
• 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薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。