此文章仅仅记录用法学习,不从源码与线程概念分析
一、协程开启方式
1.使用runBlocking代码块
runBlocking {
//耗时操作
}
注意:不建议使用,此方法开启的协程仍然是主线程,如果有在此做耗时操作,会阻塞主线程
使用场景:测试用
2.使用CoroutineScope(Dispatchers.IO),参数可以选择在io线程还是main线程来,根据启动方式可以返回job或者Def类型,job中有相关api比如cancel、join等等,def返回的是有值函数,和java中的future类似
//方式1
val job = CoroutineScope(Dispatchers.IO).launch {
//耗时操作
}
//方式2
val def = CoroutineScope(Dispatchers.IO).async {
//耗时操作
}
注意:协程需要在activity销毁的时候取消,避免内存泄漏,如下
job.cancel()
def.cancel()
使用场景:io数据库操作、网络请求等
3.全局协程,单例,同样有launch和asyn方式
val job = GlobalScope.launch(Dispatchers.IO){
//耗时操作
}
注意:此协程是单例且全局性的,尽量少用,容易造成内存泄漏。
使用场景:应用不会频繁调用的耗时操作,且不太用关注结果的,比如比较耗时的初始化操作
4.lifecycleScope.launch(Dispatchers.IO),默认绑定生命周期的协程,同样有有launch和asyn方式
lifecycleScope.launch(Dispatchers.IO){
//耗时操作
}
使用场景:和activity强绑定的耗时操作,比如页面的网络加载、数据库加载等等
5.使用MainScope().launch(Dispatchers.IO),同样有launch和asyn方式
MainScope().launch(Dispatchers.IO){
//耗时操作
}
注意:协程需要在activity销毁的时候取消,避免内存泄漏
6.viewModel绑定的协程,一般用于MVVM结构,跟随viewmodel生命周期绑定,同样有launch和asyn方式
viewModelScope.launch(Dispatchers.IO) {
//耗时操作
}
二、用法场景
此节主要讲一下,协程中常用的withContext、delay、suspend、Deferred到底是什么意思,用法是什么,想必这也是困扰大家很多的地方
1.withContext()
协程切换,与上文的创建协程不同,withContext仅仅做协程的切换。我们举个场景,我们平时线程中做数据库访问或者网络访问后,得到了数据要进行UI修改是怎么做的?是不是比较麻烦,需要封装消息然后发送handler?
class UpdateUIHandler():Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
1 ->{
//update ui
}
}
}
}
ThreadPoolManager.getInstance().submit(Runnable {
//耗时操作
//....
val message = Message();
message.what = 1
handler.sendMessage(message)
})
网上看代码是不是很复杂,那么用协程就比较简单了,我们开启一个io协程,等到结果返回后直接withContext(Dispatcher.Main)就能切换到主线程,优化代码如下:
CoroutineScope(Dispatchers.IO).launch {
//做耗时操作
//...
withContext(Dispatchers.Main) { //切换到主线程
//update UI
}
}
是不是很简单呢?当然你如果在主线程想要切换带io线程,那么也可以withContext(Dispatcher.IO)这么来
withContext:不启动新协程,仅切换上下文,协程挂起直至块完成。
2.delay
delay这个方法其实和Thread.sleep很相似,但是区别在于delay是协程的一个特性,它是可以挂起的,且不会阻塞主线程。比如
CoroutineScope(Dispatchers.Main).launch {
delay(1000) //虽然在主线程,但是不会阻塞主线程
}
CoroutineScope(Dispatchers.Main).launch {
Thread.sleep(1000) //会阻塞主线程
}
3.suspend挂起函数
这个也是困扰了我很久。挂起函数(Suspending Function),从字面上理解,就是可以被挂起的函数。suspend 有:挂起,暂停的意思。在这个语境下,也有点暂停的意思。暂停更容易被理解,但挂起更准确。
我们举个场景:我们要获取学生的数学课程的成绩,首先根据学号获取到学生的成绩单子,再根据成绩单号查询对应的课程号,最后根据课程号查询到成绩,伪代码如下
getStudentNo(object: CallBack(){ //获取学生学号
override fun success(no: Int) {
getScoreList(object: CallBack(){ //根据学生学号获取成绩单号
override fun success(listNo: Int){
getMathScore(object:Callback() {
override fun success(MathNo: Int){ //根据成绩单号,查询数学成绩
}
override fun fail() {
}
},listNo)
}
override fun fail() {
}
},no)
}
override fun fail() {
}
})
可以看到这种情况下回调非常多,因为首先要网络请求等到学号数据返回,然后根据学号再查成绩单号,查到成绩单号后再根据这个查询数学成绩,整个过程都是网络耗时操作,且代码可读性可维护性比较差,这就是所谓的回调地狱(callback hell)
那么协程中的挂起就能很好的解决这个情况了,我们可以把代码写的很优雅,如下:
CoroutineScope(Dispatchers.Main).launch {
val studentNo = getStudentNo()
val scoreNo = getScoreList(studentNo)
val mathScore = getMathScore(scoreNo)
}
上述三个函数都用suspend修饰,拿一个举例,其中withContext会挂起,直到运行完毕才会return给主线程
suspend fun getStudentNo(): Int{
withContext(Dispatchers.IO) {
Thread.sleep(1000) //模拟耗时
}
return 114514
}
注意:
(a)suspend只有在函数内部有delay、withContext、await时才会生效,且能够真正的暂停(挂起)函数
(b)
下文中代码片的情况,无论do other是什么,1和2逻辑可以看作会同时走,哪怕包围此逻辑的代码中有suspend关键词
CoroutineScope(Dispatchers.IO).launch{
//do other1
}
//do other2
4.Deferred
是async开启协程返回的一个对象,此对象可以通过.await等待结果
val def = CoroutineScope(Dispatchers.IO).async {
Thread.sleep(4000) //模拟耗时操作
val i = 1999
i
}
val result = def.await()
工作案例需求
1.假设getScore()方法是一个访问数据库耗时操作,主线程怎么通过协程的方式来等待这个线程访问结果,且不阻塞主线程呢?
方式1:getScore()设置成suspend挂起,并使用Deferred.await
fun main() {
CoroutineScope(Dispatchers.Main).launch {
updateUI(getScore())
}
}
suspend fun getScore():Int {
val def = CoroutineScope(Dispatchers.IO).async {
Thread.sleep(3000) //模拟网络操作
100
}
return def.await()
}
方式2:访问数据库结果后通过withContext()切到主线程操作
fun getScore() {
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(3000) //模拟网络操作
val score = 100
withContext(Dispatchers.Main) { //切到主线程
updateUI(score)
}
}
}
2.funA()是一个耗时任务,执行完毕后funB()才能执行,且不能主线程阻塞
方式1:funA和funB设置挂起函数,使用withContext
suspend fun funA() {
withContext(Dispatchers.IO){
Thread.sleep(2000) //模拟耗时
}
}
suspend fun funB() {
withContext(Dispatchers.IO){
Thread.sleep(2000)
}
}
fun main(){
CoroutineScope(Dispatchers.Main).launch {
funA()
funB()
}
}
方式2:funA和funB使用deferred.await
fun funA() {
return CoroutineScope(Dispatchers.IO).async{
Thread.sleep(2000)
}
}
fun funB() {
return CoroutineScope(Dispatchers.IO).async{
Thread.sleep(2000)
}
}
fun main(){
CoroutineScope(Dispatchers.Main).launch {
funA().await() //如果不使用await(),会导致funA和funB同时执行
funB().await()
}
}
方式3:使用job的join,等待执行完毕
fun funA() {
return CoroutineScope(Dispatchers.IO).launch{
Thread.sleep(2000)
}
}
fun funB() {
return CoroutineScope(Dispatchers.IO).launch{
Thread.sleep(2000)
}
}
fun main(){
CoroutineScope(Dispatchers.Main).launch {
funA().join() //等待a执行完毕
funB()
}
}