Android中的协程(Coroutines)调研

1,483 阅读3分钟

前言

随着Kotlin的蓬勃发展,在StackOverflow网站的统计中,最受开发者欢迎的编程语言排行榜,Kotlin得到了72.6%的高比例支持。

Kotlin1.3协程正式发布,这意味着自Kotlin1.3起,协程的语言支持与API已完全稳定。

协程简介

协程官方定义

协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

协程相对于线程的优点:

在Android中新建一个线程,大约需要消耗1M的内存,如果使用线程池,线程间数据的同步是一个非长复杂的事情,所以就有了协程:

  • 可以看作是轻量级线程,创建一个协程的成本很低
  • 可以轻松的挂起和恢复操作
  • 支持阻塞线程的协程和不阻塞线程的协程
  • 可以更好的实现异步和并发

启动十万个协程,并且在一秒后,每个协程都输出一个点。现在尝试使用线程来实现,会发生什么?(很可能你的代码会产生某种内存不足的错误)

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { // 启动大量的协程
        launch {
            delay(1000L)
            print(".")
        }
    }
}

简单的理解就是,可以极大程度的简化异步操作,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 – 协程挂起。

协程在Android中的使用

Android中使用Kotlin协程,需引入kotlinx.coroutines库:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x'
    //kotlinx-coroutines-android 库中引入了 kotlinx-coroutines-core 和 kotlin-stdlib两个库

kotlin-stdlib:1.2M 使用Kotlin的项目必须引入的库

kotlinx-coroutines-core: 1.5M

kotlinx-coroutines-android: 21K

所以在我们现在使用Kotlin开发的Android项目中引入kotlinx-coroutines-android库,项目体积会增加 1.5M左右,生成APK后体积约增加600K

应用

常见场景:

在后台线程执行一个复杂任务,下一个任务依赖于上一个任务的执行结果,所以必须等待上一个任务执行完成后才能开始执行。看下面代码中的三个函数,后两个函数都依赖于前一个函数的执行结果

fun requestToken(): Token {
    // makes request for a token & waits
    return token // returns result when received 
}
fun createPost(token: Token, item: Item): Post {
    // sends item to the server & waits
    return post // returns resulting post 
}
fun processPost(post: Post) {
    // does some local processing of result
}

常见的做法是使用回调,把之后需要执行的任务封装为回调。

fun requestTokenAsync(cb: (Token) -> Unit) { ... }
fun createPostAsync(token: Token, item: Item, cb: (Post) -> Unit) { ... }
fun processPost(post: Post) { ... }
fun postItem(item: Item) {
    requestTokenAsync { token ->
        createPostAsync(token, item) { post ->
            processPost(post)
        }
    }

回调在只有两个任务的场景是非常简单实用的,很多网络请求框架的 onSuccess Listener 就是使用回调,但是在三个以上任务的场景中就会出现多层回调嵌套的问题,而且不方便处理异常。

使用Kotlin协程
suspend fun requestToken(): Token { ... }   // 挂起函数
suspend fun createPost(token: Token, item: Item): Post { ... }  // 挂起函数
fun processPost(post: Post) { ... }
fun postItem(item: Item) {
    GlobalScope.launch {
        val token = requestToken()
        val post = createPost(token, item)
        processPost(post)
        // 需要异常处理,直接加上 try/catch 语句即可
    }
}
网络请求框架Retrofit已在V2.6.0(2019-06-05)正式支持协程中的suspend关键字
@GET("users/{id}")
suspend fun user(@Path("id") id: Long): User

//获取网络数据
val user:User = Api.getUser("id")

ps:本文只做简单的协程介绍,具体使用可参考官方文档等