Gradle 进阶:动态编译技术

前面两篇文章介绍了 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 包和目录:

@Override
void 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更多的用法去做更多事情。

原文发布于微信公众号 - Android每日一讲(gh_f053f29083b9)

原文发表时间:2018-11-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏进击的程序猿

第2章:spring 依赖第2章:spring 依赖

另外在声明具体的值上,我们可以是 Straight values(primitives, Strings),也可以使idref元素,或者是对其他bean的指向,...

863
来自专栏JavaQ

深入理解Spring系列之十:DispatcherServlet请求分发源码分析

DispatcherServlet是SpringMVC的核心分发器,它实现了请求分发,是处理请求的入口,本篇将深入源码分析它的请求分发过程。可点击文末左下角“阅...

3599
来自专栏闻道于事

Mybatis,Spring,SpringMVC框架面试题

Mybatis测试 1,   Mybatis的核心是(  sqlsessionfactory    ) 2,   使用Mybatis持久化框架进行数据查询需要返...

2.1K5
来自专栏封碎

NDK入门、提高和实战 博客分类: Android AndroidLinuxJNIEclipseC#

    网上也有一些对NDK的介绍,不过都是很简单的把sample里面的例子讲解一下,并不深入,我这里把我的所得分享一下。我下载的是Android Native...

1416
来自专栏移动开发的那些事儿

Android开发之逻辑单元测试

以上createInetSocketAddress方法就是我在编写单元测试的时候单独抽离出来的方法,一方面我需要mock一个InetSocketAddress来...

1141
来自专栏Java架构师历程

spring boot应用启动原理分析

摘要: spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar...

8273
来自专栏java学习

Spring学习笔记3_Bean 获取与实例化

本章目录 Spring学习笔记3_Bean 获取与实例化 1.ApplicationContext与BeanFactory关系 2.Bean的实例化方式 ...

3158
来自专栏刘君君

Builder模式

2215
来自专栏大闲人柴毛毛

深入剖析Spring(二)——IoC容器的实现

Spring的两种IoC容器 BeanFactory 基础类型的IoC容器; 采用延迟初始化策略(容器初始化完成后并不会创建bean的对象,只有当收到初...

3554
来自专栏阿杜的世界

Spring实战3:装配bean的进阶知识主要内容:

在装配bean—依赖注入的本质一文中,我们探讨了Spring的三种管理bean的方式:自动装配、基于JavaConfig、基于XML文件。这篇文字将探讨一些Sp...

902

扫码关注云+社区

领取腾讯云代金券