前言
在我用了一段时间的 Kotlin 编写代码之后,我发现我还没用过 Kotlin 的协程,或者说是不理解什么是协程而无法去使用它,于是我就去学习了一波~
在讲 Kotlin 协程怎么用之前,我们先了解一下,什么是 Kotlin 协程。
什么是 Kotlin 协程?
你在网上搜索关键字“协程”,你大概率会搜到类似以下的描述:
- 协程和线程类似,是一种在程序开发中处理多任务的组件。
- 协程就像是一种轻量级的线程。
- 协程很像线程,但它不是线程。
- 协程是“用户态”的,它的切换不需要和操作系统交互,因此协程的切换成本比线程切换低。
- 协程由于是“协作式”的,所以不需要线程的同步操作。
看完以上描述,估计你和我当时一样的:哦,这样啊,可我还是不懂 ?
”协程“原本是一个跟”线程“非常类似的,用于处理多任务的概念,但在 Kotlin 里协程就是一套由 Kotlin 官方提供的线程API , 和 Java 的 Executor 和 Android 的 AsyncTask 一样,Kotlin 协程也对 Thread 相关的 API 做了一套封装,让我们不用过多的关心线程也可以方便的写出并发操作,这就是 Kotlin 的协程。——B站扔物线朱凯
所以说,协程到底是个啥呢?Kotlin 的协程就是线程API,用来方便执行并发操作的,下面给同学们展示一下以下几种写法~
// Thread
Thread {
// To do something...
}.start()
// Excutor
val executor = Executors.newCachedThreadPool()
executor.execute {
// To do something...
}
// Kotlin 协程
launch {
// To do something...
}
Kotlin 协程好在哪里?
本质上和其他的线程API一样,都是为了操作方便~
但不同的是,它借助了 Kotlin 得天独厚的语言优势,比那些基于 Java 实现的方案更方便一些,最重要的是它可以用看似同步的方式写出异步代码,比如下面这样。?
val weather = weatherAPi.getWeather() // 网络请求(后台线程)
weatherInfoTv.text = weather // 更新 UI (主线程、UI 线程)
Kotlin 协程与 Handler 的使用对比
下面我们来看看使用 Kotlin 协程 和 Handler 实现一个相同的代码逻辑。
- 进行 IO 操作
// Handler
val handlerThread = HandlerThread("backThread")
handlerThread.start()
val backHandler = Handler(handlerThread.looper)
backHandler.post {
// 子线程作用域,比如你要在这进行数据库或者其它的IO操作
}
// 协程
launch(Dispatchers.IO){
// 同样的,你也可以在这个协程作用域的代码块里进行IO操作
}
- UI 更新操作
// Handler
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
// 此代码块中的代码都会在主线程中执行,你可以在这里进行 UI 的更新操作
}
// 协程
launch(Dispatchers.Main){
// 同样的,你也可以在这个协程作用域里进行更新UI的操作
}
使用 Handler 连起来用,代码大概是长这样的 ↓ ↓ ↓
private fun loadData() {
// 此处代码进行了封装简化
backHandler.post {
// 同步的网络请求,或者其他IO操作
val userList = queryAllUserListFormDb()
mainHandler.post {
// 更新 UI
mAdapter.setData(userList)
}
}
}
private fun queryAllUserListFormDb(): List<User> {
// 假装进行数据库的查询操作
return mDb.queryAllUser()
}
使用Kotlin协程进行实操
我以前用 Handler 进行切线程操作的时候大概就这么写的,那么现在使用 Kotlin 协程应该怎么写呢(因为 Kotlin 协程的写法不唯一,我就挑了一个最常用的写法展示给大家了)?
private fun loadData() {
launch {
val userList = withContext(Dispatchers.IO) {
queryAllUserListFormDb()
}
mAdapter.setData(userList)
}
}
/**
* suspend 关键字:用于告诉使用者,这个方法执行的是耗时操作,仅此而已,没有其它特异功能!
*/
private suspend fun queryAllUserListFormDb(): List<User> {
// 这里假装进行用户查询的数据库操作(这里简单说一下,我们对数据库的操作本质上也是一种对文件的IO操作,
// 因为数据的持久化得放到文件里,对吧)
return mDb.queryAllUser()
}
这里使用了 suspend 关键字 和 withContext() 函数,这里再次强调一下, suspend 关键字并没有切换线程的神奇功能,切换线程这个操作是在更深层次的地方实现的(当然,我也没去看源码,听B站扔物线朱凯说的?),在 withContext() 函数的代码块里的最后一条语句的的返回值就是 withContext() 函数的返回值,所以我们可以赋值给等号左边的变量。
可以看到,使用 Handler 这样的代码就是在套娃(你用 Thread 也是差不多的),而且当你业务逻辑越来越复杂的时候,套娃可能就套得更深了。
比如说你要等一个请求的数据回来之后再和另一个请求返回的数据进行合并的时候,你大概只能选择同步请求的方式进行,但你没有办法,你就是得这么干( RxJava 除外,但是它的操作符太多了,学起来会比 Kotlin 协程更不容易一些),但使用 Kotlin 协程的话,你就可以直接顺着写下去。
同学们,很神奇有木有?反正我觉得这样用起来比回调地狱(套娃)爽多了,所以你学废了吗?