又掌握了一项新技能 - 断点调试 Gradle 插件

前言

最初开发Android应用程序的时候,肯定是在打log调试,然后慢慢地觉得打log效率太低下了,不能快速定位问题,于是走上了断点调试之路。Gradle插件也一样,从会写插件那一刻起到现在,一直用的是打log调试功能,但是同样的这种方式效率也太低下了,这之前,我也尝试过寻找断点调试的方式,但是一直没有成功,昨天偶然之间调通了,于是记录一发。

之前失败的方式

之前测试断点调试的功能的时候,一直在build.gradle中直接测代码,测试的代码也是在project.afterEvaluate之后,然后遍历android..applicationVariants,获取各个信息,就像这样子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
dependencies {
compile gradleApi()
compile localGroovy()
}
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.UnitTestVariant
import com.android.build.gradle.internal.variant.ApplicationVariantData
import com.android.build.gradle.internal.api.ApplicationVariantImpl
project.afterEvaluate {
if (project.plugins.hasPlugin("com.android.application")) {
def android = project.extensions.getByName("android")
android.applicationVariants.all {ApplicationVariantImpl variant ->
project.logger.error "DebuggerPlugin:${variant}"
ApplicationVariantData apkVariantData = variant.getApkVariantData()
ApplicationVariantData variantData = variant.getVariantData()
TestVariant testVariant = variant.getTestVariant()
UnitTestVariant unitTestVariant = variant.getUnitTestVariant()
}
}
}

于是这个断点我打了一年硬是没打住,恩,没错,这个东西我断断续续实验了一年也没有成功过,也有点无语,原因也不知道。昨日发现,必须得在外面包一层plugin才能打住断点,就像这样子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
dependencies {
compile gradleApi()
compile localGroovy()
}
apply plugin: DebuggerPlugin
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.UnitTestVariant
import com.android.build.gradle.internal.variant.ApplicationVariantData
import com.android.build.gradle.internal.api.ApplicationVariantImpl
class DebuggerPlugin implements Plugin<Project> {
void apply(Project project) {
project.afterEvaluate {
if (project.plugins.hasPlugin("com.android.application")) {
def android = project.extensions.getByName("android")
android.applicationVariants.all {ApplicationVariantImpl variant ->
project.logger.error "DebuggerPlugin:${variant}"
ApplicationVariantData apkVariantData = variant.getApkVariantData()
ApplicationVariantData variantData = variant.getVariantData()
TestVariant testVariant = variant.getTestVariant()
UnitTestVariant unitTestVariant = variant.getUnitTestVariant()
}
}
}
}
}

具体原因也找不到,理论上来讲,两者没有什么大的区别,除非不包plugin的代码编译后代码位置发生了变化,导致打不到断点。

其实这事也怪自己,如果一开始直接用插件项目来测,将其发布到本地maven,然后执行去打断点,估计老早就成功了,硬是在build.gradle中写零碎的代码来测试,往事不提也罢。

一个坑

以上代码执行过程会先出现一个错,如下:


1

Error:The closure '[email protected]' is not valid as an action for argument 'com.and[email protected]1103b69d'. It should accept no parameters, or one compatible with type 'com.android.build.gradle.internal.api.ApplicationVariantImpl_Decorated'. It accepts (com.android.build.gradle.internal.api.ApplicationVariantImpl).

这个错出现的时候,只需要将ApplicationVariantImpl variant改成def variant即可,然后继续运行,会出现另一个问题。

即出现了一个cannot cast object with class A to A的问题,如下


1

Error:Cannot cast object 'ApplicationVariantData{debug}' with class 'com.android.build.gradle.internal.variant.ApplicationVariantData' to class 'com.android.build.gradle.internal.variant.ApplicationVariantData'

可以看到虽然两个对象obj1和obj2的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被虚拟机认为是相同的,所以抛出了ClassCastException异常。

那么怎么解决呢,将项目中所有buildscript中的dependencies下引用的android gradle plugin 版本都改成同一个,即


1
2
3
4
5
6
7
8

buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:${global_gradle_plugin_version}"
}
}

之后将~/.gradle/daemon/目录下内容全部删除,然后看看是否解决了,如果没有解决,则继续删除~/.gradle/daemon/,然后重启电脑。TM的如果还没好,那么请确定项目中所有引用的插件中的compile的android gradle plugin版本是否都一致,如果不一致,请保持一致,不然也有问题。

这问题就算完事了,大概可能和Gradle的守护进程、插件引用的android gradle plugin版本不一致有那么一点关系。

断点调试方式1

说完了以上坑,正式进入断点调试的环节,方式一很简单,直接利用gradle的参数让其等待我们的调试进程attach上去。比如我要执行gradle clean这个task,则加上两个额外参数即可

  • 一个是开启debug
  • 一个是不使用守护进程

    具体例子如下:

    
    
    1
    
    
    gradle :app:clean -Dorg.gradle.debug=true --no-daemon

    之后这个进程就会一直等待,直到我们attach我们的调试进程。如下图所示:

    又掌握了一项新技能 - 断点调试 Gradle 插件

然后参考这篇文章Intellij-IDEA远程调试,利用Android Studio或者Intellij IDEA的remote debug进行调试,端口号填5005.

如图

又掌握了一项新技能 - 断点调试 Gradle 插件

然后运行remote

又掌握了一项新技能 - 断点调试 Gradle 插件

之后就会attach上去我们的进程

又掌握了一项新技能 - 断点调试 Gradle 插件

然后看看效果

又掌握了一项新技能 - 断点调试 Gradle 插件

从此可以愉快的断点调试了。

断点调试方式2

和方式一差不多,只不过不是用gradle的参数来开启debug,而是用环境变量


1

export GRADLE_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"

之后就跟正常执行任务一样


1

gradle clean

剩下的操作和方式1一样。

http://fucknmb.com/2017/07/05/%E5%8F%88%E6%8E%8C%E6%8F%A1%E4%BA%86%E4%B8%80%E9%A1%B9%E6%96%B0%E6%8A%80%E8%83%BD-%E6%96%AD%E7%82%B9%E8%B0%83%E8%AF%95Gradle%E6%8F%92%E4%BB%B6/