Android动态编译技术:Plugin Transform Javassist操作Class文件

时间:2024-03-18 16:55:01

###一、原理介绍

1、App构建是将代码编译为.class文件,然后打包成dex文件之后输出apk

2、Gradle构建App由一个个Task组成,每个Task作用实际上是接收一个输入(编译App所需的资源)然后进行处理然后有一个输出

3、Gradle1.5以后提供了transform-api可以在代码转化为.class文件之后再打包成dex文件之前对它进行处理

4、Javassist可以处理.class文件

所以我们可以通过自定义gradle插件的方式利用javassist在打包的过程中修改.class文件,这样编译出来的apk文件中就会是我们修改过的class

###二、Transfrom-API介绍

Android动态编译技术:Plugin Transform Javassist操作Class文件

getName:用于指明本Transform的名字,这个 name 并不是最终的名字,在TransformManager 中会对名字再处理

getInputTypes:用于指明Transform的输入类型,可以作为输入过滤的手段

    –CLASSES表示要处理编译后的字节码,可能是 jar 包也可能是目录

     –RESOURCES表示处理标准的 java 资源

getScopes:用于指明Transform的作用域

    –PROJECT                         只处理当前项目

    –SUB_PROJECTS  只处理子项目

    –PROJECT_LOCAL_DEPS  只处理当前项目的本地依赖,例如jar, aar

    –EXTERNAL_LIBRARIES  只处理外部的依赖库

    –PROVIDED_ONLY  只处理本地或远程以provided形式引入的依赖库

    –TESTED_CODE                         只处理测试代码

isIncremental:用于指明是否是增量构建。

transform:核心方法,用于自定义处理,在这个方法中我们可以拿到要处理的.class文件路径、jar包路径、输出文件路径等,拿到文件之后就可以对他们进行操作

利用Transform-api处理.class文件有个标准流程,拿到输入路径->取出要处理的文件->处理文件->移动文件到输出路径

 

Android动态编译技术:Plugin Transform Javassist操作Class文件

Transform-api处理流程

上图展示的代码中没有包含处理过程,我们只需要在FileUtils.copy函数之前对拿到的文件进行处理即可

###三、javassist介绍

    介绍:Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

    常用类

    ClassPool:javassist的类池,使用ClassPool类可以跟踪和控制所操作的类,它的工作方式与 JVM类装载器非常相似,

    CtClass: CtClass提供了检查类数据(如字段和方法)以及在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。不

        过,    Javassist 并未提供删除类中字段、方法或者构造函数的任何方法。

    CtField:用来访问域

    CtMethod :用来访问方法 

   CtConstructor:用来访问构造器

    基本用法

         1、添加类搜索路径

          ClassPool pool =ClassPool.getDefault();

           pool.insertClassPath("/usr/local/javalib");

        2、添加方法

         CtClass point =ClassPool.getDefault().get("Point");

         CtMethod m =CtNewMethod.make( "public int xmove(int dx) { x += dx; }", point);point.addMethod(m);

        3、修改方法

         CtClass point =ClassPool.getDefault().get("Point"); 

         CtMethod m= point.getDeclaredMethod(“show", null)

         m.insertAfter(“System.out.prinln(“x:” + x + “,y:) + y”))

        4、添加字段

         CtClass point =ClassPool.getDefault().get("Point");

          CtField f = newCtField(CtClass.intType, "z", point);

          point.addField(f);

###四、自定义Gradle插件

    自定义插件可以直接新建一个名为buildSrc的module不过这样的插件只能自己工程用,还有另外一种方式可以发布到jcenter提供别别人用也可以发布到本地自己使用。这里是第二种方式的步骤:这里是原文

    1、新建一个 Module,此Module 用于开发插件,类型选什么都无所谓,后面会大改。

    2、在 Project 目录视图模式下,清空build.gradle 文件的内容,删除其余的所有文件。

    3、然后在 module 中新建多个文件夹 src/main/groovy ,再新建包名文件夹。 在 main 目录下再新建resources 目录,在resources 目录下再新

    建 META-INF 文件夹,再新建文件夹gradle-plugins,这样就完成了 gradle插件的目录结构搭建

 

Android动态编译技术:Plugin Transform Javassist操作Class文件

Module文件结构

    4、打开 build.gralde 文件,替换全部内容

 

Android动态编译技术:Plugin Transform Javassist操作Class文件

Build.gradle内容

    5、编写插件内容。在刚刚新建的包名下 再次新建一个文件 MyPlugin.groovy ,注意文件类型,一定是 groovy 类型文件

    6、在resources/META-INF/gradle-plugins 目录下新建一个 .properties 文件,注意该文件的命名就是你使用此插件时的名称,这里命名

    为 com.app.myplugin.properties ,一定要注意后缀名称,那么使用时的名称就是com.app.myplugin,文件里面的内容填写如下:     implementation-class=com.app.plugin.MyPlugin,这里是 key = value 的形式,值就是刚刚自定义的 插件( groovy 文件 )的全名,也就是径加上类名称。

    7、发布插件到本地,修改build.gradle文件,这个时候右侧的 gradle Toolbar 就会在module下多出一个task :upload-uploadArchives。 clean工程然后运行upload-uploadArchives

 

Android动态编译技术:Plugin Transform Javassist操作Class文件

发布插件

    8、引用插件

 

Android动态编译技术:Plugin Transform Javassist操作Class文件

引用插件

      a、修改工程根目录下的build.gradle

       b、在module的目录下的build.gradle中添加

      apply plugin:'com.app.plugin.myplugin' //这里就填写.properties 文件的名称

###五、实操

    我们可以按照上面的步骤进行一次demo编写,这里要做的功能是在代码中所有的View.OnClickListener类的onClick方法中插入一个Toast,在每次编译之后我们可以到文件夹D:\as_workspace\sample\javassist\app\build\intermediates\transforms\ModifyTransform\debug下查看.class文件是否修改成功,D:\as_workspace\sample\javassist\这个是代码工程目录ModifyTransform是自定义Gradle插件的名称。修改class文件在实际中还是挺有用处的,比如我们可以用来做无埋点或者修改第三方jar包中的bug。

代码:https://github.com/yaozhukuang/javassist

借鉴:https://blog.csdn.net/yulong0809/article/details/77752098