Gradle 进阶:动态编译技术

1,044 阅读5分钟
原文链接: mp.weixin.qq.com

前面两篇文章介绍了 Gradle自定义插件以及扩展配置的用法。今天我们来看一下一个具体的应用场景,动态编译。我们将尝试在编译期间修改class文件。

初识Transform

Android Gradle 工具在 1.5.0 版本后提供了 Transfrom API, 允许第三方 Plugin 在打包 dex 文件之前的编译过程中操作 .class 文件。目前 jarMerge、proguard、multi-dex、Instant-Run 都已经换成 Transform 实现。

如下图:

具体怎么操作呢?我们在自定义的Gradle插件中先创建一个自己的Transform。重写的transform方法处就是处理class文件的时机。

public class MyTransform extends Transform {  Project project  MyTransform(Project project) {    this.project = project  }  @Override  void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {    //todo  }  @Override  String getName() {    return "MyTransform"  }  @Override  Set<QualifiedContent.ContentType> getInputTypes() {    return TransformManager.CONTENT_CLASS  }  @Override  Set<QualifiedContent.Scope> getScopes() {    return TransformManager.SCOPE_FULL_PROJECT  }  @Override  boolean isIncremental() {    return false  }}

当然我们希望做点什么,例如修改我们类中的某个方法,或加入一行log。(尤其当你需要修改一些你没有源码修改权限的第三方Jar包时)我们需要借助另一个工具,javassist。

javassist

先配置一下插件的gradle

dependencies {  compile gradleApi()  compile localGroovy()  compile 'com.android.tools.build:gradle:3.2.1'  compile 'com.android.tools.build:transform-api:1.5.0'  compile 'javassist:javassist:3.12.1.GA'  compile 'commons-io:commons-io:2.5'}

然后创建一个插入的代码类MyInjects,该类的作用就是往MainActivity中插入一句log-->"Hello world!"

public class MyInjects {private static ClassPool pool = ClassPool.getDefault()private static String injectStr = "System.out.println(\"Hello world!\" ); "public static void injectDir(String path) {    pool.appendClassPath(path)    File dir = new File(path)    if (dir.isDirectory()) {        dir.eachFileRecurse { File file ->            String filePath = file.absolutePath            if (filePath.endsWith("MainActivity.class")) {                String className = "com.example.huhu.kotlindemo.ac.MainActivity"                CtClass c = pool.getCtClass(className)                if (c.isFrozen()) {                    c.defrost()                }                CtConstructor[] cts = c.getDeclaredConstructors()                if (cts == null || cts.length == 0) {                    CtConstructor constructor = new CtConstructor(new CtClass[0], c)                    constructor.insertBeforeBody(injectStr)                    c.addConstructor(constructor)                } else {                    cts[0].insertBeforeBody(injectStr)                }                c.writeFile(path)                c.detach()            }         }      }   }}

接着在Transform中调用,transform的inputs包含了两个部分 jar 包和目录:

@Overridevoid transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {    inputs.each { TransformInput input ->        input.directoryInputs.each { DirectoryInput directoryInput ->            MyInjects.injectDir(directoryInput.file.absolutePath)            def dest = outputProvider.getContentLocation(directoryInput.name,                    directoryInput.contentTypes, directoryInput.scopes,                    Format.DIRECTORY)            FileUtils.copyDirectory(directoryInput.file, dest)        }        input.jarInputs.each { JarInput jarInput ->            def jarName = jarInput.name            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)        }    }}

最后,我们看一下,在Gradle Plugin中如何注册Transform:

class DemoPlugin implements Plugin<Project> {    @Override    void apply(Project project) {        def android = project.extensions.getByType(AppExtension)        android.registerTransform(new MyTransform(project))    }}

然后,我们运行App以后,就可以发现:

 I/System.out: Hello world!

这只是一种简单的用法,大家可以查阅javassist更多的用法去做更多事情。