调试注解处理器
背景
有很多第三方库通过注解来自动生成一些代码,比如我们常用的ButterKnife、ARouter等框架,这些框架就是用到了注解处理器。处于好奇或者说为了解决实际问题,我们可能需要调试这些框架的注解处理器的源码,网上大部分都在讲怎么自定义注解处理器,讲调试的比较少或者说一笔带过。本人前段时间使用ButterKnife时遇到了一个问题,于是决定debug ButterKnife注解处理器的源码,最终问题是解决了,是8.5.1版本butterknife的一个bug导致的,升级到8.8.1就行。调试期间踩了一些坑,现在分享出来。
调试环境搭建
在说具体的调试环境搭建之前,我先讲下我对java代码调试的理解。java代码的调试可以分为两个部分,第一个是调试器,负责搜索源代码,根据断点暂停程序执行等;第二个是虚拟机,就是执行字节码的那个虚拟机。首先调试器连接到虚拟机,然后程序跑起来时,调试器和虚拟机就会进行交互以便完成debug这个事情。我个人猜测,这个交互的流程应该是这样的:首先虚拟机会将当前执行字节码的一些信息传给调试器,这些信息应该会包含类名以及行号,调试根据类名搜索工程里的源文件,然后根据断点信息在适当的时候通知虚拟机暂停执行,往后就是单步调试了。当然了,实际情况可能要复杂很多。
ok,理解了Java代码的调试之后,我们以ButterKnife为例说明如何搭建注解处理器的调试环境。
一、引入jar包
首先找到ButterKnife注解处理器的jar包,这个jar包我是到gradle缓存里找到的。我电脑的路径如下:
cache_path.png找缓存有个技巧这里一起分享给大家:
首先as里打开三方库的某个类,然后as切换为project模式,再定位到该类,然后右击选择file_path,就可以定位到gradle缓存的路径了。
找到jar包后,放到app的libs目录下,然后在app.gradle文件中添加如下代码:
compileOnly files('libs/butterknife-compiler-8.5.1.jar')
annotationProcessor files('libs/butterknife-compiler-8.5.1.jar')
compile "com.jakewharton:butterknife:8.5.1"
annotationProcessor "com.jakewharton:butterknife-compiler:8.5.1"
这里依赖的作用我一一做下说明:
1、第一个依赖是为了我们可以展开注解处理器的jar包,以便打断点:
2、因为第一个依赖的jar包包含注解处理器,所以as会提示我们一定要用annotationProcessor明确配置,所以第二个依赖是为了解决as报错。
3、第三个依赖很熟悉了,依赖远程的ButterKnife。
4、第四个看起来是多余的,其实不然,第二个依赖只是解决as报错,单独的一个butterknife-compiler-8.5.1.jar是跑不起来的,会报如下错误:
本人猜测应该是单独的jar缺少了某些东西,因此需要跑远程的注解处理器。
ok,每个依赖都说了作用,实际大家可以灵活处理,比如如果就在项目里调试,有些依赖已经存在就可以不用再依赖了。
二、配置远程调试器
上面说了,java代码的调试是调试器和虚拟机之间相互交互完成的。这里我们首先针对调试器进行配置。
说起调试器,其实我们一直在和它亲密接触:
上面的两个按钮相信大家都很熟了,它们就会启动调试器,但是这里启动调试器需要明确指定连接哪个虚拟机,比如你指定了某个进程,其实就是指定了调试器连接到对应进程的虚拟机。针对本文的场景,这就不太好搞了,因为运行注解处理器代码的虚拟机在哪我们并不知道。解决办法就是远程调试,远程调试的原理就是以虚拟机为服务端,让它监听某个端口,而调试器作为客户端连接到虚拟机,他们之间以socket进行通信。因此,首先我们要配置一个远程调试器。
远程调试器的配置过程如下:
1、首先打开Edit Config界面
2、然后点击加号,选择remote,进行如下配置
remote_config.png3、最后点击ok,就配置好了一个远程调试器,稍后会启动它。
三、配置远程虚拟机
配置远程虚拟机,其实就是配置虚拟机的启动参数,也就是要让注解处理器代码运行的虚拟机按上面复制的参数启动,这样虚拟机就会监听指定的端口,等待调试器的连接。
具体步骤如下:
1、首先在as右边找到下面这个task:
这里说下,为什么是这个task呢?是因为这个task的执行包含了注解处理器task的执行。那你说我用build task行不行,也可以的,但是没有必要,因为build这个task太"重量级"了,我们就调试个注解处理器,没必要走整个build流程。
2、接着右击选择create xxxxx
把suspend改成y是让虚拟机执行前等待调试器的连接。
四、开始连接
ok,以上配置完毕,就可以开始连接调试了。
首先运行刚才配置的compileDebugJavaWithJavac
然后你会看到一直在转圈,其实就是虚拟机在等待调试器连接。
切换配置,按debug那个按钮就可以启动远程调试器了
ok,到此,程序就会在断点处停下了,记得提前断点哦。
result.png进阶调试
上述流程走完,就可以愉快的debug butter刀的注解处理器源码了,但是很快就会发现:class代码真难看啊。。。。
解决办法是有的,上面介绍的流程,调试器和虚拟机是在一个工程里做的,其实这个是没有局限的,因为调试器和虚拟机是使用socket通信的,所以配置在不同工程是可行的,甚至不同电脑都可以。既然如此,那我们就可以在一个工程里让注解处理器的代码运行的虚拟机以某个端口进行启动,然后clone butterknife的源码工程,再在这个工程里配置调试器,只要端口对上,就可以连接调试了。具体笔者就不展开了,笔者实测是可行的。不过有个问题就是,clone下来的最新源码可能跟你依赖的代码的版本不一致,这样调试可能行号就对不上了。笔者的建议是可以先看源码搞清楚原理,再debug class代码解决实际问题。
后记
还有一种情况是调试自定义的注解处理器,其实基本原理和上面一样的,大家只要理解好调试器和虚拟机的关系就行。另外,有时候断点了没停下来,可能是因为缓存导致相关的task略过去了,这时候只要动一动注解相关的代码,再重新运行上面配置的Task就可以了。