前言

协程(Coroutines),是个让人​​又爱又恨​​的东西。代码写起来是真的,调试起来是真的

本文将介绍 Kotlin 协程的​​调试技巧​​,不会涉及太多协程实际内容。所以,不管你有没有协程的基础,都可以看下去,如果有遇到不懂的概念直接忽略即可,后面我会系统讲解。

这篇文章是为我们协程系列打基础的,后面进入我们的协程部分​​《图解协程》​​​,具体的写作计划大家可以到这里看看:​​《Kotlin Jetpack 实战:目录》​​,欢迎提建议。

2. 前期准备

  • 将 Android Studio 版本升级到最新
  • 将我们的 Demo 工程 clone 到本地,用 Android Studio 打开:​​github.com/chaxiu/Kotl…​
  • 切换到分支:​​chapter_08_coroutine_debug​
  • 强烈建议各位小伙伴小伙伴跟着本文一起实战,实战才是本文的精髓

3. 协程 JVM 参数

协程,可以理解为轻量级的线程。协程跟线程的关系,有点像“线程与进程的关系”。

在我们写 Java 并发代码的时候,我们经常会用 ​​Thread.currentThread().name​​ 带打印出当前线程的名字,然后通过日志来查看运行效果。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "index = " + index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}

/*
pool-1-thread-1index = 0
pool-1-thread-3index = 2
pool-1-thread-2index = 1
pool-1-thread-2index = 3
pool-1-thread-3index = 4
*/

对于 Kotlin 协程,我们也可以做类似的事情。具体做法是,添加 JVM 参数:​​-Dkotlinx.coroutines.debug​​。具体做法如下:

  • 1.点击 IDE 的​​Edit Configurations​

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_android

  • 2.将​​-Dkotlinx.coroutines.debug​​​填入​​VM options​​ 中:

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_ide_02

在加上这个参数以后, 我们通过 ​​Thread.currentThread().name​​,就会自动为我们输出当前协程的名字。

// 这段代码看不懂没关系,忽略即可
// 请直接看后面的输出信息和注释
fun main() {
runBlocking<Unit> {
fun log(msg: Any) {
println("${Thread.currentThread().name} msg=$msg")
}

log(1)

launch {
val a = 4
delay(300)
log(a)
}
launch {
val b = 3
delay(200)
log(b)
}
launch {
val c = 2
delay(100)
log(c)
}
}
}

/*
// 输出:

1个线程 4个协程
↓ ↓
main @coroutine#1 msg=1
main @coroutine#4 msg=2
main @coroutine#3 msg=3
main @coroutine#2 msg=4
*/

小结

  • 一个线程可以对应多个协程
  • 协程将线程划分成更小的单元
  • 协程跟线程的关系,有点像“线程与进程的关系”。

看到这里,也许会有小伙伴说:“我还是搞不懂协程到底是什么,输出一个日志,能说明什么问题呢?”

确实,我在刚学习协程的时候,也有这种苦恼,​​协程太抽象了,比线程还抽象​​​。线程它起码有一个 ​​Thread.java​​ 的源码给你看,协程呢,没有,Kotlin 编译器将它底层细节都屏蔽了。

​那么,有没有更直观的方式来调试协程呢?​​有的,只是目前国内知道的人应该不多。

前几天我在 ​​Kotlin 官方的博客​​ 中了解到:Kotlin 官方在 1.4 版本中为协程调试增加了许多支持。虽然 Kotlin 1.4 现在还没正式发布,但是我们可以抢先体验它的 RC 版本(Release Candidate Version)。

4. Kotlin 1.4 协程调试

Kotlin 1.3 最让我惊喜的是 ​​Flow​​​,而 Kotlin 1.4 最让我惊喜的则是​​“协程调试支持”​​。

在 Kotlin 1.4 之前,我一直都是通过 JVM 参数来研究协程的,这种方式并不友好,有的时候为了理解协程的代码,我需要加很多无关的 log。

一起看看 Kotlin 1.4 的变化:

Kotlin 1.4 之前

Kotlin 1.4

断点

断点经常不生效

​稳定​

单步调试

单步调试经常不生效

​稳定​

单独的协程调试窗口

不支持

​支持​

查看协程创建栈

不支持

​支持​

查看协程调用栈

不支持

​支持​

协程挂起状态

不支持

​支持​

协程内存信息 dump

不支持

​支持​

虽然 Kotlin 1.4 尚未发布,但这并不影响我们提前探索使用 Kotlin 1.4,毕竟​​早用早享受​​嘛。

4-1 升级 Kotlin 版本

首先,将 Kotlin 相关的库升级到最新的:​​1.4-RC​

const val kotlinVersion = "1.4.0-rc"
const val coroutines = "1.3.8-1.4.0-rc"

4-2 Kotlin EAP 渠道

接下来,我们需要升级 IDE 自带的 Kotlin 插件版本:

  • ​1.​​​首先进入:​​Configure Kotlin Plugin Updates​

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_ide_03

  • ​2.​​​然后,在接下来的页面中,在​​Updates channel​​​ 下拉框中选择:​​1.4.x​​​,点击 check(需要梯子),接着 IDE 会提示你有新的 Kotlin 版本,你点击:​​install​​ 安装,然后重启即可。(如果你在看这篇文章的时候,1.4 正式版已经发布了的话,这一步就可以省略了。你只需要确保 IDE 是最新的即可。)

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_android_04

4-3 协程的断点设置

  • ​3.​​​接下来在 IDE 中找到我们的示例代码,打一个断点,然后​​右键点击​​断点,在弹出框中按照如下设置:

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_ide_05

4-4 协程调试窗口

  • ​4.​​然后就可以开始调试了,等程序停留在断点的位置后,会是这样。到目前为止,都还没有看见我们想要的协程调试窗,这时候需要我们手动点击红色箭头的位置:

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_java_06

  • ​5.​​​在弹出的窗口中,我们需要勾选​​coroutines​​:

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_java_07

4-5 协程的调试栈

  • ​6.​​勾选以后,我们就立马能看到一个专属于协程的调试框:

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_Kotlin_08

在上面的调试框中,我们能看到我们的代码当前有三个协程,其中​​1​​​的位置,代表:​​corouine2, coroutine3 的状态是 Suspend​​​;​​2​​​的位置,代表​​corouine4 的状态是 Running​​​;​​3​​的位置,是该协程的创建栈。

4-6 协程 Dump

  • ​7.​​​正如我前面所说的,协程跟线程很像,所以它们两者的调试框也是类似的。比如,你还可以​​Dump​​ 当前协程的内存状态,方便分析。

Kotlin Jetpack 实战 | 08. 协程“不为人知”的调试技巧_android_09

由于我们使用的并非正式的版本,Dump 出来的信息还比较少,相信之后官方对​​协程调试​​的支持会越来越好。

5. 总结

  • ​工欲善其事,必先利其器​​。在正式学习协程之前,我们先学会协程调试技巧,这将对我们后面学习协程有极大的帮助。
  • 协程,并不是什么洪水猛兽,你完全可以将它看作一种​​更轻量级的线程​​。这句话我以后会反复讲,有些东西看起来复杂,本质却极其简单,协程就是这样的。
  • 本文使用的是​​1.4-rc​​ 版本,写博客做研究是没问题的,但在生产环境是不推荐使用的。