Gradle插件开发(2) - extensions和Taskjava
通过前边两篇介绍,咱们了解了Gradle的基础知识和如何写一个本身的插件,咱们今天,开始实战,搞点有趣的东西。android
今天前面的介绍,咱们是能够实现本身的自定义task,当时android在构建是一个很琐碎的过程,以前的各个环节都是task,为了让开发人员更少也更好写业务代码,后来出了一套Transform API
。git
Transform
是特地为Android打造的,按照官网的解释以下:github
Starting with 1.5.0-beta1, the Gradle plugin includes a Transform API allowing 3rd party plugins to manipulate compiled class files before they are converted to dex files. (The API existed in 1.4.0-beta2 but it's been completely revamped in 1.5.0-beta1)api
The goal of this API is to simplify injecting custom class manipulations without having to deal with tasks, and to offer more flexibility on what is manipulated. The internal code processing (jacoco, progard, multi-dex) have all moved to this new mechanism already in 1.5.0-beta1.
Note: this applies only to the javac/dx code path. Jack does not use this API at the moment.app
简单解释下就是:ide
Transforms
是从新引入的,主要做用在对class的处理上,也是在生成dex文件前。Transforms
有很强大功能,避免了你们使用task,内部能够处理jacoco, progard, multi-dex
等过程。在Android studio下咱们如果执行./gradlew tasks
就会发现不少以那么Transforms
开头的task。函数
咱们能够注册多个transform,这个相似于task
流式关系。 这写API能够参考如何理解 Transform API,能够认为transform
是码头搬运的师父,你加入一我的,不影响个总体的码头的云端,前提是你要按照码头的规矩办事。 那么transform
遵循码头的那些规矩呢?固然就是Transforms
的API了,你要严格按照这个API定义每一步。 那么Transforms 有哪些API呢:工具
public class XXXTransform extends Transform {
@Override
public String getName() {
return null;
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return null;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return null;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
}
}
复制代码
来解释下每一个函数的做用:测试
getName
定义transform
的名字,便于程序区分getInputTypes
拦截输入的流的类型,哪些它会拦截呢?public class TransformManager extends FilterableStreamCollection {
public static final Set<ContentType> CONTENT_CLASS;
public static final Set<ContentType> CONTENT_JARS;
public static final Set<ContentType> CONTENT_RESOURCES;
public static final Set<ContentType> CONTENT_NATIVE_LIBS;
public static final Set<ContentType> CONTENT_DEX;
public static final Set<ContentType> CONTENT_JACK;
}
复制代码
TransformManager
:public static final Set<Scope> SCOPE_FULL_PROJECT = Sets.immutableEnumSet(
Scope.PROJECT,
Scope.PROJECT_LOCAL_DEPS,
Scope.SUB_PROJECTS,
Scope.SUB_PROJECTS_LOCAL_DEPS,
Scope.EXTERNAL_LIBRARIES);
public static final Set<QualifiedContent.ScopeType> SCOPE_FULL_INSTANT_RUN_PROJECT =
new ImmutableSet.Builder<QualifiedContent.ScopeType>()
.addAll(SCOPE_FULL_PROJECT)
.add(InternalScope.MAIN_SPLIT)
.build();
public static final Set<Scope> SCOPE_FULL_LIBRARY = Sets.immutableEnumSet(
Scope.PROJECT,
Scope.PROJECT_LOCAL_DEPS);
复制代码
transform(TransformInvocation transformInvocation)
是整个类的核心,inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须作否则编译会报错。Transform
注册一个Transform
到项目里,须要经过Plugin
实现,
public class TranPlugin implements Plugin<Project> {
void apply(Project project) {
System.out.println("------------------开始----------------------");
//AppExtension就是build.gradle中android{...}这一块
def android = project.extensions.getByType(AppExtension)
//注册本身的Transform
def classTransform = new ClassTransform(project);
android.registerTransform(classTransform);
System.out.println("------------------结束了吗----------------------");
}
}
复制代码
Transform
public class ClassTransform extends Transform {
private Project mProject;
public ClassTransform(Project p) {
this.mProject = p;
}
/** transform的名称 * transformClassesWithMyClassTransformForDebug 运行时的名字 */
@Override
public String getName() {
return "MyClassTransform";
}
//须要处理的数据类型,有两种枚举类型
//CLASSES和RESOURCES,CLASSES表明处理的java的class文件,RESOURCES表明要处理java的资源
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
/** * 指Transform要操做内容的范围,官方文档Scope有7种类型: * EXTERNAL_LIBRARIES 只有外部库 * PROJECT 只有项目内容 * PROJECT_LOCAL_DEPS 只有项目的本地依赖(本地jar) * PROVIDED_ONLY 只提供本地或远程依赖项 * SUB_PROJECTS 只有子项目。 * SUB_PROJECTS_LOCAL_DEPS 只有子项目的本地依赖项(本地jar)。 * TESTED_CODE 由当前变量(包括依赖项)测试的代码 */
@Override
public Set<QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
/** * 指明当前Transform是否支持增量编译 */
@Override
public boolean isIncremental() {
return false;
}
/** * Transform中的核心方法 * inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。 * outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须作否则编译会报错 */
@Override
public void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
System.out.println("----------------进入transform了--------------")
System.out.println("--------------结束transform了----------------")
}
}
复制代码
若这样,什么都不写,是咱们的transform
会报错,咱们能够将遍历input
文件,:
//遍历input
inputs.each {
TransformInput input ->
//遍历文件夹
input.directoryInputs.each {
DirectoryInput directoryInput ->
// 获取output目录
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
println(directoryInput.file.getPath() + " ---> " + dest.toPath())
// 将input的目录复制到output指定目录
FileUtils.copyDirectory(directoryInput.file, dest)
}
////遍历jar文件 对jar不操做,可是要输出到out路径
input.jarInputs.each {
JarInput jarInput ->
// 重命名输出文件(同目录copyFile会冲突)
def jarName = jarInput.name println("jar = " + jarInput.file.getAbsolutePath()) def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0, jarName.length() - 4)
}
def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(jarInput.file, dest)
}
}
复制代码
能够看到咱们什么都没作,就是copy,不过copy文件是须要从新命名一个文件。
这样就能够将码头搬运过程当中,从上一个工人人的东西,直接传给了下一个工人,只是有少些的改动。 那咱们如果想在其中进行少些的改动呢?这就须要用到一个神奇的工具javassist
。
Javaassist能够用 Javassist 改变 Java 类的字节码,而无需真正了解关于字节码或者 Java 虚拟机(Java virtual machine JVM)结构的任何内容,这样他就能够直接修改class文件的字节码。
Javassist
使您能够检查、编辑以及建立 Java 二进制类。检查方面基本上与经过 Reflection API 直接在 Java 中进行的同样,可是当想要修改类而不仅是执行它们时,则另外一种访问这些信息的方法就颇有用了。这是由于 JVM 设计上并无提供在类装载到 JVM 中后访问原始类数据的任何方法,这项工做须要在 JVM 以外完成。
Javassist 使用 javassist.ClassPool 类跟踪和控制所操做的类。这个类的工做方式与 JVM 类装载器很是类似,可是有一个重要的区别是它不是将装载的、要执行的类做为应用程序的一部分连接,类池使所装载的类能够经过 Javassist API 做为数据使用。
装载到类池中的类由 javassist.CtClass 实例表示。与标准的 Java java.lang.Class 类同样, CtClass 提供了检查类数据(如字段和方法)的方法。不过,这只是 CtClass 的部份内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist 没有提供删除一个类中字段、方法或者构造函数的任何方法。
字段、方法和构造函数分别由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的全部方法的方法,包括方法或者构造函数中的实际字节码内容。
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("me.cyning.cc.Rectangle");
cc.setSuperclass(pool.get("me.cyning.cc.Point"));
cc.writeFile();
复制代码
这样就将test.Rectangle
的父类设置为test.Point
,其中 pool.get("test.Rectangle")
就是用默认的ClassPool来装寨对应的class,而且可使用装载的CtClass
.
调用 writeFile() 后,这项修改会被写入原始类文件. 怎么验证呢?
Class rc = cc.toClass();
System.out.println(rc.getSuperclass());
复制代码
咱们能够寨函数的前面和后面插入咱们的值,如何作呢?
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("me.cyning.Hello");
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"before Hello.say():\"); }");
m.insertAfter("{ System.out.println(\"after Hello.say():\"); }");
Class c = cc.toClass();
Hello h = (Hello)c.newInstance();
h.say();
}
public class Hello {
public void say() {
System.out.println("Hello");
}
}
复制代码
先将me.cyning.Hello
load到Javassist的ClassPool中,获取到me.cyning.Hello
类中的say
方法,能够在方法开始和结尾来加入函数。 是否是有点意思。
因而咱们能够在咱们类中直接使用Javassist来修改咱们的class字节码。 接着transform
中的transform
方法来处理,全部的class到transform
方法中都是以class文件存在的,因此拦截文件夹。
只须要在适当的方法里注入以下代码:
``````````````````````println("方法名 = " + mMethod.getName())
println("返回类型 = " + mMethod.getReturnType().getName())
// println("参数类型 = " + mMethod.getParameterTypes())
// 开始时间
mMethod.addLocalVariable("startMs", CtClass.longType);
mMethod.insertBefore("startMs = System.currentTimeMillis();");
String body = "{" +
"final long endMs = System.currentTimeMillis();" +
"System.out.println(\"Executed in ms: [" + className + "," + mMethod.getName() +
"] ---> \" + (endMs-startMs));}"
println(body)
mMethod.insertAfter(body)
复制代码
找到对应的class的看下,生效:
最后的代码: github.com/ownwell/Gra…
Transform API Android Plugin Transform 初探 Gradle经过Transform API实现代码注入 深刻理解 Android 之 Gradle Tutorial 1 Android ASM 插桩初步实现 Android热修复技术——QQ空间补丁方案解析(3)
网易乐得-Android AOP之字节码插桩