开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
(一)初识协程
协程是什么?
- 协程基于线程,它是轻量级线程
- coroutine是cooperation(协作)和routine(日常)的简写
- 协程让异步逻辑同步化,杜绝回调地狱
- 协程的核心是:函数或一段程序被挂起,之后再在挂起的位置恢复
- 协程的实现分为基础设施层和业务框架层,类似java中的NIO和Netty
- 业务框架层指的是我们常用的协程函数
基础设施层(原生api)实现例子
private fun doContinuation() {
//协程体
val continuation = suspend {
println("协程执行中...")
"协程的返回值"
}.createCoroutine(object : Continuation<String> {
override fun resumeWith(result: Result<String>) {
//回调
println("协程执行结束: $result")
}
override val context: CoroutineContext = EmptyCoroutineContext
})
continuation.resume(Unit)
}
如上代码使用的是:import kotlin.coroutines.* 而kotlin协程业务框架层使用的是:import kotlinx.coroutines.*
Android中协程解决了什么问题?
- 处理耗时任务,这种任务常常会阻塞主线程
- 保证主线程安全,确保安全的在主线程调用suspend函数
在Android 11 谷歌建议使用协程来替代异步任务(asynctask)
协程的挂起与恢复
常规函数包括:invoke(call)和return,协程新增了suspend和resume
suspend:挂起或暂停,表示暂停执行当前协程,并保存所有局部变量 resume:让已暂停的协程从暂停处恢复执行
使用 suspend关键字修饰的函数叫挂起函数 挂起函数只能在协程体内或其他挂起函数内调用
挂起和阻塞
挂起:挂起点先记录下来,然后去做耗时任务,做完以后再恢复 阻塞:不做别的事情,一直等待
private fun doGlobalScope() {
//如果没有指定调度器,那么默认就是使用:Dispatchers.Default(default也是非主线程)
GlobalScope.launch(Dispatchers.Main) {
//挂起(不会阻塞主线程,比如按钮按下去会立马弹起来,然后6秒后打印)
delay(6000)
Log.v("zx", "${Thread.currentThread().name},挂起6秒后")
}
//阻塞(会阻塞主线程,比如按钮按下去要等5秒后才能弹起来)
Thread.sleep(5000)
Log.v("zx", "${Thread.currentThread().name},阻塞5秒后")
}
主线程在遇到挂起点后,可以不用等待直接更新UI,但是遇到阻塞就必须等待阻塞完成之后
协程的调度器 Dispatchers
所有协程必须的调度器中运行
Dispatchers.Main:主线程,处理UI交互和轻量级任务(调用suspend,调用UI函数,更新liveData) Dispatchers.IO:非主线程,为磁盘和网络IO进行了优化(数据库,文件读写,网络请求) Dispatchers.Default:非主线程,专为CPU密集型任务进行了优化(数组排序,json解析,差异判断)
任务泄露
当某个协程任务丢失,导致内存,cpu资源浪费,称为任务泄露,为了避免协程泄露,Kotlin引入了结构化并发机制。 结构化并发的作用:取消任务,追踪任务,发送错误信号
结构化并发
定义协程,必须指定其CoroutineScope,它会跟踪所有协程,还可以取消由它所启动的所有协程。 常用的api有:
GlobalScope:生命周期是process级别的,即使Acitivty和fragment已经销毁,协程仍然在执行,GlobalScope是一个顶级协程,不太建议直接用 MainScope:在Activity中使用,可以在onDestroy中取消协程 viewModelScope:只能在viewModel中使用,绑定viewModel的生命周期 lifecycleScope:只能在Activity和fragment中使用,会绑定Activity和Fragment的生命周期
MainScope使用案例:
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val mainScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
doMainScope()
}
private fun doMainScope() {
//retrofit不用写withcontext(Dispatchers.IO)因为retrofit会自动侦察到,
//如果你是挂起函数会自动启用协程,创建一个异步线程去做操作
mainScope.launch {
//Toast.makeText(this@MainActivity, "MainScope", Toast.LENGTH_SHORT).show()
try {
//retrofit请求
delay(1000)
} catch (e: Exception) {
//调用 mainScope.cancel()取消协程会抛出异常
e.printStackTrace()
}
}
//MainActivity 继承了委托以后CoroutineScope by MainScope(),就可以直接使用launch了
launch {
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
//class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {}
//MainActivity 继承了委托以后CoroutineScope by MainScope(),就可以直接使用cancle
cancel()
}
}
viewModelScope使用案例
class MyViewModel:ViewModel() {
private val _count = MutableLiveData<Int>()
val count: LiveData<Int>
get() = _count
fun getUser(){
//因为继承了ViewModel,那么就可以直接使用viewModelScope
viewModelScope.launch {
//如果是耗时操作的话,retrofit会自动起一个io线程来执行,可以省略withContext(Dispatchers.IO)
delay(3000)
_count .value = 123
}
}
}
调用
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val myViewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initViewModelAndLiveData()
}
private fun initViewModelAndLiveData() {
myViewModel.count.observe(this, Observer {
binding.btnRandom.text = it.toString()
})
myViewModel.getUser()
}
}