为什么需要协程
举例一个异步编程中最常见的场景:后台线程执行一个A任务,下一个B任务依赖于A任务的执行结果,所以必须等待上一个任务执行完成后才能开始执行。直接上代码。
// 模拟本地创建 Token
fun requestToken(cb: (Token) -> Unit){
//todo
}
//组装Http请求
fun createHttpUtils(token: Token, item: Item, cb: (HttpUtils) -> Unit): {
//todo
}
//进行网络请求
fun postUrl(httpUtils: HttpUtils) {
//todo
}
如果在代码中调用 会怎么样呢?
fun postItem() {
requestToken{ token ->
createHttpUtils(token) { httpUtils->
postUrl(httpUtils)
}
}
}
可以看到 我们 嵌套了好几层来实现一个网络请求,可读性变差,代码也很容易出现问题。kotlin中的 协程可以帮助我们减少代码嵌套,优化线程/线程池控制。
协程使用
修改示例的方法,引入kotlin 中的协程我们再看一下 代码会发生哪些变化
// 关于suspend 后边总结,挂起函数的重点
suspend fun requestToken(): Token { ... } // 挂起函数
// 挂起函数
suspend fun createHttpUtils(token: Token): HttpUtils{ ... }
fun postUrl(httpUtils: HttpUtils) { ... }
// 代码使用
fun postItem() {
// 注意:开发过程中不能直接使用GlobalScope.launch
GlobalScope.launch {
// 看似三个任务并行处理的,但其实是串联执行的
val token = requestToken()
val httpUtils= createHttpUtils(token)
postUrl(httpUtils)
}
}
上述代码,像代码中的GlobalScope.launch不建议在日常开发中使用,此处只是为了简单代码展示。因为 GlobalScope 创建的协程没有父协程,除非执行完成或手动取消,该协程都会继续运行,而实际上在 Android 中协程的运行需要跟 Activity/Fragment 的生命周期绑定。
看完上边的代码使用,就已经可以在项目中简单使用了
了解协程
项目中引入
// 协程 https://github.com/Kotlin/kotlinx.coroutines
kotlin_coroutines : [group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-android', version: versions.kotlin_coroutines],
kotlin_coroutines_core : [group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: versions.kotlin_coroutines],
协程概念
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。 - 协程官方文档翻译
协程的开发人员 Roman Elizarov 如下描述协程的:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
协程知识点
根据官方网站了解重要的知识点
挂起函数
//有suspend修饰符标记,这表示两个函数都是挂起函数
suspend fun requestToken(): Token { ... }
suspend fun createHttpUtils(token: Token): HttpUtils{ ... }
挂起函数能够以与普通函数相同的方式获取参数和返回值,但是调用函数可能挂起协程,挂起函数挂起协程时,不会阻塞协程所在的线程。挂起函数执行完成后会恢复协程,后面的代码才会继续执行。但是挂起函数只能在协程中或其他挂起函数中调用。
知识点
要想明白协程的内在,还是需要看源码
GlobalScope.launch
// launch 函数源码
// CoroutineContext: 协程上下文 是一些元素的集合,包括 Job 和 CoroutineDispatcher 等元素。
// CoroutineDispatcher : 协程调度器,决定协程所在的线程或线程池 (类型 :Dispatchers.Default、Dispatchers.IO、Dispatchers.Main和Dispatchers.Unconfined)
// CoroutineScope.launch 默认使用Dispatchers.Default
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
对于源码简单的解释 主要看注释部分。
创建方式
- CoroutineScope.launch : CoroutineScope.launch {} 是最常用的 Coroutine builders,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器
- runBlocking : runBlocking {}是创建一个新的协程同时阻塞当前线程,直到协程结束。(个人很少用....)
- ** withContext**: withContext {}不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成。
- **async ** : 类似CoroutineScope.launch,区别是 async 返回的Deferred 类型,Deferred 是 Job的子类,带有返回值,但是 CoroutineScope.launch 返回的 Job类型,所以 CoroutineScope.launch没有返回值。