【Kotlin】协程(一)——入门

介绍

这里一开始不打算介绍什么是协程,虽然标题叫介绍~~html

为了方便理解,这边先作个比喻: 从使用的角度来看,Kotlin的协程像是“另外一种RxJava”,可是比RxJava要高效。java

这里先有个大体的印象,先了解下协程在实际中的做用,回头再去看它的原理,或许会更容易些。android

一开始查了好多关于协程资料(包括官方完档),发现不一样的人说的不大同样,最后越看越乱。因而我决定一开始先不说什么是协程。后端

做用

上面说到,协程用起来“像是另外一种RxJava”。api

那么是否是能够用协程来开启一个异步操做?切换线程? 答案是确定的,不只能够作到,并且写起来也很简单。下面看个栗子网络

栗子

举个例子,这里有个登陆操做,须要用两个接口才能完成。 一、使用帐号密码去获取token 二、经过token获取用户信息异步

很明显,这是个嵌套的请求。代码立刻就浮如今脑海中,因而咱们埋头“papapa”,很快就写出了这样的一段:async

reqToken(new CallBack<String>() { //请求token
    @Override
    public void onSuccess(String token) {
        reqUserInfo(token, new CallBack<UserInfo>() { //经过token,获取用户信息
            @Override
            public void onSuccess(UserInfo userInfo) {
                Logger.Companion.i("login success");
            }
        });
    }
});
复制代码

是的,确实没什么问题。不过没以为这要的代码很长吗?ide

因而咱们改用lambda简写,或是kotlin:函数

reqToken{ //请求token
    reqUserInfo(it){  //经过token,获取用户信息
        Logger.i("login success")
    }
}
复制代码

nice,瞬间简洁了好多

确实简洁了不少。不过仍是难逃嵌套结构,若是多来几层,最后可能成了这样:

蜜汁嵌套

看得头皮发麻~~

可是!!!,若果用协程就不同了(划重点)

coroutineScope.launch {
    val token = getToken()
    val userInfo = getUserInfo(token)
    Logger.i("login success")
}
复制代码

不只代码少,并且能够用同步的方式来写异步!!!

往下再了解一点?

使用

知道到了他的优(niu)秀(bi)之处,下面来看看是怎么用的

由于是Kotlin的协程,因此项目须要支持Kotlin。怎么支持就不用我说了吧? (不要问我,我不会,由于那是另外一个同事作的。hahaha~~~)

倒入依赖

gradle倒入协程依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
复制代码

建立协程做用域CoroutineScope

能够直接new一个MainScope

val mainScop = MainScope()
复制代码

注意记得在销毁的时候调用cancel(),调用cancel()后用mainScope启动的协程都会取消掉。

override fun onDestroy() {
    super.onDestroy()
    mainScope.cancel()
}
复制代码

建立一个协程

经常使用的方式有两种:launch()async(),下面分别来讲明他们的用途。

固然还有其余的建立方式,这里就不说了。

launch

使用launch()建立一个协程,返回一个Job对象。

val job = mainScope.launch {
    //在协程中的操做
    Logger.i("launch end")
}
复制代码

很简单,mainScope.launch{}就能建立一个协程,大括号中的代码是在协程中执行的。

看下打印的日志,发现这个协程时在主线程中运行的。

"这有什么用?在主线程中运行的协程?那我再里面作耗时操做,是否是会卡住?"

确实,若是直接这样用时会阻塞主线程的。因此这时候,就须要用到withContext()

这里用的是上面的mainScope,这个做用域内的调度器是基于主线程调度器的。也就是说,mainScope.launch()获得的协程默认都是在主线程中。因此println("in main scope")是在主线程中运行的

withContext

withContext():**用给定的协程上下文调用指定的暂停块,暂停直到完成,而后返回结果。**也就是说,能够用来切换线程,并返回执行后的结果。

经常使用的有 Dispatchers.Main:工做在主线程中 Dispatchers.Default:将会获取默认调度器(子线程) Dispatchers.IO:IO线程 Dispatchers.Unconfined:是一个特殊的调度器(说实话,我没搞懂他的用法~~)

这里子线程中请求一个token,而后回到主线程中:

mainScope.launch {
    val token = withContext(Dispatchers.Default) {
        Logger.i("get token")
        val token =  api.getToken()//注意!!!这是个同步的网络请求
        token
    }
    Logger.i("token $token")
}
复制代码

再来看下日志

有withContext()后,线程的切换显得是那么简单。只要你开心,能够切来切去。

mainScope.launch {
    withContext(Dispatchers.Default) {
        Logger.i("切到子线程")
    }
    withContext(Dispatchers.Main) {
        Logger.i("切到主线程")
    }
    withContext(Dispatchers.IO) {
        Logger.i("切到IO线程")
    }
    Logger.i("launch end")
}
复制代码

async

除了launch(),还有个经常使用的方法——async()async()launch()类似。不一样的是他能够返回协程执行结束后值。

async()返回的是一个Deferred对象,须要经过Deferred#await()获得返回值。

仍是上面的例子:子线程中请求一个token,而后回到主线程中:

mainScope.launch {
    val tokenDeferred = async(Dispatchers.Default) {
        Logger.i("get token")
        val token =  api.getToken()//注意!!!这是个同步的网络请求
        token //返回token
    }
    val token = tokenDeferred.await()
    Logger.i("token : $token")
}
复制代码

打印的结果上面同样,就不贴图了。

async()launch()同样,都能指定执行的线程。

因为Deferred#await()须要在协程中调用,因此上面在launch()中使用async()

“这有什么用?跟launch()差很少啊?”

额~~ 用处大了,往下看

suspend

若是切换线程中的代码不少,想把(withContext(){...})的代码抽出来。因而写成这样

fun getToken(): String {
    return withContext(Dispatchers.Default) {
        //同步请求获得token
        val token = api.getToken()
        token
    }
}
复制代码

BUT,并不能这样用,发现编译器报错了:

发现 withContext()只能在 协程或** suspend**方法中使用。因此,在方法前加上 suspend就不会报错了。

suspend fun getToken(): String { ... }
复制代码

suspend:申明这是个可挂起的函数,里面能够用协程的一下方法(launch()、async()、withContext()等)。

实际应用

有了协程,写异步的代码将会方便不少。

串行的请求

回到一开始的栗子,请求token,而后用token请求UserInfo

mainScope.launch {
    //获取token
    val token = withContext(Dispatchers.Default) {
        val token = api.getToken()
        token 
    }
    //经过token,获取userInfo
    val userInfo = withContext(Dispatchers.Default) {
        val userInfo = api.getUserInfo(token)
        userInfo 
    }
    //登陆成功
    Logger.i("login success, token: $token, userInfo is null: ${userInfo == null}")
}
复制代码

看到这里,你可能会:“不对啊,一开始的栗子没这么复杂~~~”

由于上面的例子中,把请求那部分的代码抽到suspend方法去了。

mainScope.launch {
    //获取token
    val token = getToken()
    //经过token,获取userInfo
    val userInfo = getUserInfo(token)
    //登陆成功
    Logger.i("login success, token: $token, userInfo is null: ${userInfo == null}")
}
---------------------------------------
suspend fun getUserInfo(token: String): UserInfo {
    return withContext(Dispatchers.Default) {
        Logger.i("get userInfo, token: $token")
        val userInfo = api.getUserInfo(token)
        userInfo
    }
}
suspend fun getToken(): String {
    return withContext(Dispatchers.Default) {
        Logger.i("get token")
        val token = api.getToken()
        token
    }
复制代码

稍微调整下,就会发现和上面是栗子是同样的

并行的请求

有时候,遇到“优秀”的后端同窗。一个页面须要请求两个接口,用两个接口返回的数据才能渲染出页面。

这里发起两个连续的请求也能够作到,可是若是能够变成两个并行的请求,岂不美哉?

那么,async()就能够排上用场了。

mainScope.launch {
    val timeMillis = measureTimeMillis { //记录耗时
        val deferred1 = async { getData1() }
        val deferred2 = async { getData2() }
        val data1 = deferred1.await()
        val data2 = deferred2.await()
        Logger.i("data1: $data1, data1: $data2")
    }
    Logger.i("timeMillis : $timeMillis")
}
--------------------------------------------------
suspend fun getData1(): String {
    return withContext(Dispatchers.Default) {
        Thread.sleep(1000)
        "value1"
    }
}
suspend fun getData2(): String {
    return withContext(Dispatchers.Default) {
        Thread.sleep(1000)
        "value2"
    }
}
复制代码

查看日志

会发现,getData2()getData1()都是延迟1000ms的请求,若是用串行的方式来写,耗时确定超过2000ms。使用async()耗时也才1051ms。

这里用measureTimeMillis()来计算代码耗时。

总结

协程基本的使用到这里就能够告一段落了,主要介绍了协程给我带来了什么,能够在什么场景下用,怎么用。相信这样同步的方式来写异步,这样写出来的代码必定是很是直观、清晰的。

然而,有关什么是协程?有哪些详细的用法和细节?进程、线程和协程又有什么关系?留着后面后面再说。

参考

Kotlin 官网 Kotlin Coroutines(协程) 彻底解析 Kotlin Primer·第七章·协程库 探究高级的Kotlin Coroutines知识 破解 Kotlin 协程

以上有错误之处,感谢指出

相关文章
相关标签/搜索