Kotlin Coroutines(协程)讲解,2024年最新面试官撕简历
Coroutine**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是
userService.doLoginAsync(username, password) { user ->
userService.requestCurrentFriendsAsync(user) { friends ->
val finalUser = user.copy(friends = friends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE
}
}
步骤如下:
- 显示一个进度条;
- 请求服务器校验用户名和密码;
- 等待校验成功后,请求服务器获取好友列表;
- 最后,隐藏进度条;
情况还可以更复杂,想象一下,不仅要请求好友列表,还需要请求推荐好友列表,并把两次结果合并进一个列表。
有两种选择:
- 最简单的方式就是,在请求完好友列表之后,再请求推荐好友列表,但是这种方式不够高效,因为后者并不依赖前者的请求结果;
- 这种方式相对复杂一些,同时请求好友列表和推荐好友列表,并同步两次请求的结果;
通常情况下,想要偷懒的人可能会选择第一种方式:
progress.visibility = View.VISIBLE
userService.doLoginAsync(username, password) { user ->
userService.requestCurrentFriendsAsync(user) { currentFriends ->
userService.requestSuggestedFriendsAsync(user) { suggestedFriends ->
val finalUser = user.copy(friends = currentFriends + suggestedFriends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE
}
}
}
到这里,代码开始变得复杂了,出现了可怕的回调地狱:后一个请求总是嵌套在前一个请求的结果回调里面,缩进变得越来越多。
由于使用的是 Kotlin 的 lambdas,可能看起来并没有那么糟糕。但是随着请求的增多,代码变得越来越难以管理。
别忘了,我们使用的还是一种相对简单但并不高效的一种方式。
什么是协程(Coroutine)
简单来说,协程像是轻量级的线程,但并不完全是线程。
首先,协程可以让你顺序地写异步代码,极大地降低了异步编程带来的负担;
其次,协程更加高效。多个协程可以共用一个线程。一个 App 可以运行的线程数是有限的,但是可以运行的协程数量几乎是无限的;
协程实现的基础是可中断的方法(suspending functions)。可中断的方法可以在任意的地方中断协程的执行,直到该可中断的方法返回结果或者执行完成。
运行在协程中的可中断的方法(通常情况下)不会阻塞当前线程,之所以是通常情况下,因为这取决于我们的使用方式。具体下面会讲到。
coroutine {
progress.visibility = View.VISIBLE
val user = suspended { userService.doLogin(username, password) }
val currentFriends = suspended { userService.requestCurrentFriends(user) }
val finalUser = user.copy(friends = currentFriends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE
}
上面的示例是协程的常用使用范式。首先,使用一个协程构造器(coroutine builder)创建一个协程,然后,一个或多个可中断的方法运行在协程中,这些方法将会中断协程的执行,直到它们返回结果。
可中断的方法返回结果后,我们在下一行代码就可以使用这些结果,非常像顺序编程。注意实际上 Kotlin 中并不存在 coroutine 和 suspended 这两个关键字,上述示例只是为了便于演示协程的使用范式。
可中断的方法(suspending functions)
可中断的方法有能力中断协程的执行,当可中断的方法执行完毕后,接着就可以使用它们返回的结果。
val user = suspended { userService.doLogin(username, password) }
val currentFriends = suspended { userService.requestCurrentFriends(user) }
可中断的方法可以运行在相同的或不同的线程,这取决于你的使用方式。可中断的方法只能运行在协程中或其他可中断的方法中。
声明一个可中断的方法,只需要使用 suspend 保留字:
suspend fun suspendingFunction() : Int {
// Long running task
return 0
}
回到最初的示例,你可能会问上述代码运行在哪个线程,我们先看这一行代码:
coroutine {
progress.visibility = View.VISIBLE
…
}
你认为这行代码运行在哪个线程呢?你确定它是运行在 UI 线程吗?如果不是,App 就会崩溃,所以弄明白运行在哪个线程很重要。
答案就是这取决于协程上下文(coroutine context)的设置。
协程上下文(Coroutine Context)
协程上下文是一系列规则和配置的集合,它决定了协程的运行方式。也可以理解为,它包含了一系列的键值对。
现在,你只需要知道 dispatcher 是其中的一个配置,它可以指定协程运行在哪个线程。
dispatcher 有两种方式可以配置:
- 明确指定需要使用的
dispatcher; - 由协程作用域(
coroutine scope)决定。这里先不展开说,后面会详细说明;
具体来说,协程构造器(coroutine builder)接收一个协程上下文(coroutine context)作为第一个参数,我们可以传入要使用的 dispatcher。因为 dispatcher 实现了协程上下文,所以可以作为参数传入:
coroutine(Dispatchers.Main) {
progress.visibility = View.VISIBLE
…
}
现在,改变进度条可见性的代码就运行在了 UI 线程。不仅如此,协程内的所有代码都运行在 UI 线程。那么问题来了,可中断的方法会怎么运行?
coroutine {
…
val user = suspended { userService.doLogin(username, password) }
val currentFriends = suspended { userService.requestCurrentFriends(user) }
…
}
这些请求服务的代码也是运行在主线程吗?如果真是这样的话,它们会阻塞主线程。到底是不是呢,还是那句话,这取决于你的使用方式。
可中断的方法有多种办法配置要使用的 dispatcher,其中最常用的方法是 withContext。
withContext
在协程内部,这个方法可以轻易地改变代码运行时所在的上下文。它是一个可中断的方法,所以调用它会中断协程的执行,直到该方法执行完成。
这样以来,我们就可以让示例中那些可中断的方法运行在不同的线程中:
suspend fun suspendLogin(username: String, password: String) =
withContext(Dispatchers.Main) {
userService.doLogin(username, password)
}
上面这些代码会运行在主线程,所以仍然会阻塞 UI 。但是,现在我们可以轻易地指定使用不同的 dispatcher:
suspend fun suspendLogin(username: String, password: String) =
withContext(Dispatchers.IO) {
userService.doLogin(username, password)
}
现在我们使用了 IO dispatcher, 上述代码会运行在子线程。另外,withContext 本身就是一个可中断的方法,所以,我们没必要让它运行在另一个可中断方法中。所以我们也可以这样写:
val user = withContext(Dispatchers.IO) { userService.doLogin(username, password) }
val currentFriends = withContext(Dispatchers.IO) { userService.requestCurrentFriends(user) }
目前为止,我们认识了两个 dispatcher,下面我们详细介绍一下所有的 dispatcher 的使用场景。
-
Default: 当我们未指定
dispatcher的时候会默认使用,当然,我们也可以明确设置使用它。它一般用于 CPU 密集型的任务,特别是涉及到计算、算法的场景。它可以使用和 CPU 核数一样多的线程。正因为是密集型的任务,同时运行多个线程并没有意义,因为 CPU 将会很繁忙。 -
IO: 它用于输入/输出的场景。通常,涉及到会阻塞线程,需要等待另一个系统响应的任务,比如:网络请求、数据库操作、文件读写等,都可以使用它。因为它不使用 CPU ,可以同一时间运行多个线程,默认是数量为 64 的线程池。Android App 中有很多网络请求的操作,所以你可能会经常用到它。
-
UnConfined: 如果你不在乎启动了多少个线程,那么你可以使用它。它使用的线程是不可控制的,除非你特别清楚你在做什么,否则不建议使用它。
-
Main: 这是 UI 相关的协程库里面的一个
dispatcher,在 Android 编程中,它使用的是 UI 线程。
现在,你应该可以很灵活地使用各种 dispatcher 了。
协程构造器(Coroutine Builders)
现在,你可以轻松地切换线程了。接下来,我们学习一下如何启动一个新的协程:当然要靠协程构造器了。
根据实际情况,我们可以选择使用不同的协程构造器,当然我们也可以创建自定义的协程构造器。不过通常情况下,协程库提供的已经满足我们的使用了。具体如下:
runBlocking
这个协程构造器会阻塞当前线程,直到协程内的所有任务执行完毕。这好像违背了我们使用协程的初衷,所以什么场景下会用到它呢?
runBlocking 对于测试可中断的方法非常有用。在测试的时候,将可中断的方法运行在 runBlocking 构建的协程内部,这样就可以保证,在这些可中断的方法返回结果前当前测试线程不会结束,这样,我们就可以校验测试结果了。
fun testSuspendingFunction() = runBlocking {
val res = suspendingTask1()
assertEquals(0, res)
}
但是,除了这个场景外,你也许不会用到 runBlocking 了。
launch
这个协程构造器很重要,因为它可以很轻易地创建一个协程,你可能会经常用到它。和 runBlocking 相反的是,它不会阻塞当前线程(前提是我们使用了合适的 dispatcher)。
这个协程构造器通常需要一个作用域(scope),关于作用域的概念后面会讲到,我们暂时使用全局作用域(GlobalScope):
GlobalScope.launch(Dispatchers.Main) {
…
}
launch 方法会返回一个 Job,Job 继承了协程上下文(CoroutineContext)。
Job 提供了很多有用的方法。需要明确的是:一个 Job 可以有一个父 Job,父 Job 可以控制子 Job。下面介绍一下 Job 的方法:
job.join
这个方法可以中断与当前 Job 关联的协程,直到所有子 Job 执行完成。协程内的所有可中断的方法与当前 Job 相关联,直到子 Job 全部执行完成,与当前 Job 关联的协程才能继续执行。
val job = GlobalScope.launch(Dispatchers.Main) {
doCoroutineTask()
val res1 = suspendingTask1()
val res2 = suspendingTask2()
process(res1, res2)
}
job.join()
job.join() 是一个可中断的方法,所以它应该在协程内部被调用。
job.cancel
这个方法可以取消所有与其关联的子 Job,假如 suspendingTask1() 正在执行的时候 Job 调用了 cancel() 方法,这时候,res1 不会再被返回,而且 suspendingTask2() 也不会再执行。
val job = GlobalScope.launch(Dispatchers.Main) {
doCoroutineTask()
val res1 = suspendingTask1()
val res2 = suspendingTask2()
process(res1, res2)
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。






既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:
他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。
刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。
技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!
提升自己去挑战一下BAT面试难关吧

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
不论遇到什么困难,都不应该成为我们放弃的理由!
如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!
最后祝各位新人都能坚持下来,学有所成。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
助**。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
不论遇到什么困难,都不应该成为我们放弃的理由!
如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!
最后祝各位新人都能坚持下来,学有所成。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-WQZ8gtRw-1712797371170)]
更多推荐



所有评论(0)