版权声明:javascript
本帐号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影全部。java
未经容许,不得转载。android
回顾一下,以前说的美团的无埋点方案,是重写须要的UI 控件,而后在其中监听对应的事件,事件触发的时候,上报统计点便可。以前也讲解了,如何使用 AppCompatDelegate 来替换咱们项目内的系统控件。api
本文就,如何使用一个 Gradle Plugin(如下简称 Plugin),来实如今编译期间,修改 class 字节码的功能,作一个简单的讲解。函数
由于涉及的点比较多,因此有一些地方只是点一下,不会详细深刻说明,有兴趣的话,能够看看文内推荐的文章,或者自行搜索相关资料。工具
Gradle 是一个自动化构建工具,可使用一种基于 Groovy 的特性领域语言(DSL)来声明项目设置。Gradle 也提供了一些默认的 Plugin 帮助咱们构建项目,若是想要在构建期间定制的操做,就须要单独开发一款和本身功能相关的 Gradle Plugin。post
而 Gradle Plugin,是可使用 Groovy、Java、Scala 进行开发的。自己对 Scala 不熟悉,通常我都是使用 Groovy 来开发 Gradle 插件,而 Groovy 又是能够和 Java 代码混编的,上手还算容易。gradle
Gradle 的插件,能够理解为编写的一个库,因此它和咱们编写的 Library同样,有在项目内供本项目使用的,也有能够发布出去,供其余项目使用的。ui
这两种方式的区别:spa
Gradle 的构建过程,是一个链式的过程,A → B → C,这的一个过程。也就是说,咱们依赖 Gradle Plugin,来完成咱们指定的任务,就须要了解到,咱们的操做是须要插入到 Gradle 构建过程当中的那一步。
Project 接口是你写的插件代码和 Gradle 交互的主要接口,经过 Project 能够在插件内,使用 Gradle 特性,而 Project 与 build.gradle 文件是一对一的关系。
在 Gradle 中,经过 Extension 在不一样的 Gradle Plugin 之间传递处理后的结果。
例如上面,就是一个 Plugin 的入口,用到了 Project 来操做 Gradle 的构建过程,在其中注册了一个 Transform,这个 Transform 才是在编译期间修改 class 字节码的关键。
前面提到,开发 Gradle Plugin 的时候,必定要明确须要在什么地方作什么操做。
而从 Gradle 1.5.0 版本开始,Android 的 Gradle 插件中就引入了 Transform API 。和上面链式调用的思想同样,Transform 每次都会获得一个输入,而后作对应的处理,再将输出的结果,输出出去,做为下一个 Transform 的输入。
Transform 的相关内容,能够查阅文档:
tools.android.com/tech-docs/n…
因此咱们在上面,使用 registerTransform() 注册了一个咱们本身的 Transform ,供咱们在编译期间作对应的修改。
Transform 是一个虚类,须要对其进行实现。而最重要的方法就是 transform()。
这差很少算是一个标准实现,能够看到它须要区分出是当前项目内的包,还有第三方库的 Jar 包,进行单独处理。
已经明确,能够在 Transform 中修改 class 字节码,而作这个修改就须要用到:Javassit。
固然这里不是必定须要使用 Javassit ,其余的字节码操做库应该也能够,例如:ASM。
Javassist 能够彻底替换一个方法或者构造函数的字节码正文,这里就不展开讨论了。具体 Javassist 的使用,能够自行查阅资料。
使用 Javassist 还须要在 build.gradle 中添加依赖关系:
compile 'org.javassist:javassist:3.20.0-GA'复制代码
推荐一篇讲解 Javassist 的文章:
既然关键技术点已经介绍过了,那么就以一个简单的例子,试着编写一个 Gradle Plugin ,在其中修改其内的 class 字节码,最简单的,在构造方法中添加一行代码。
建立一个空项目,只有一个 Activity 页面。开始编写注入逻辑。
这里查找到须要的 class 文件以后,首先判断是否有构造方法,若是没有构造方法就建立一个,而后在其构造方法内注入一行代码。
在 Transform.transform() 中,调用注入的方法。
在主项目中,添加这个 Gradle Plugin 插件。
最终运行以后,咱们来看看反编译后的效果。
可是这样实际上并无办法修改第三方库里的类,这个须要咱们特殊处理。前面已经提到,在 Transform 中,须要区分当前项目的目录,而针对第三方库的 Jar 包,咱们须要先对其进行解压,而后修改完成以后,再压缩回去。
接下来在 Demo 中,新建一个叫"lib" 的 module ,在其内只有一个类,编译成 Jar 包,在主项目中引用它。
那么咱们开始编写解压的逻辑。
而后再从新压缩成 Jar 包的方法也跟上。
最后,咱们还须要修改 Transform.transform() 方法。
这里为了方便,指定须要解压的 Jar 包,而且解压在根目录下。最终会从新打包成一个 Jar 包,给主项目引用。
接下来看看反编译后的 apk 。
能够看到,对 Jar 包内的类,用这样的方式也是能够将其修改的。
最后看看整个项目的目录结构。
有须要 Demo 源码的朋友,能够在本公众号回复 "GradlePlugin" 得到。
实际上,完整的替换方案,会比这里复杂不少。有须要能够先了解一下这些技术细节再尝试编写接下去的逻辑,有时间的话,以后再继续分享其涉及到的细节。
本文参加掘金技术征文:juejin.im/post/58d8e9…