1.协程是什么?
kotlin协程是一套高效处理异步任务的框架,通过挂起函数实现切换线程,并在异步任务处理完成之后自动切换回原来的线程,并且异步任务的处理不会阻塞原来线程的执行。
2.怎么使用协程?
2.1 使用协程框架中的launch方法包裹的代码块就是协程的内容,常规的代码如下:
val coroutineScope = CoroutineScope(Dispatchers.Main)
coroutineScope.launch {
getImage(imageId)
}
这种方式指定了在协程的调度器在主线程执行,以便更新UI。
2.2 使用LifecycleScope启动协程
LifecycleScope适合在Activity和Fragment中启动协程,会感知Activity和Fragment生命周期的变化,会在ondestroy的时候自动取消协程的执行。
2.3 使用ViewModelScope启动协程
在实际的项目中,我们通常使用ViewModel.viewModelScope来直接使用launch函数,并做适当的封装:
// 这里定义了一个高阶函数,便于其他调用的地方直接传入携程代码块
//viewModelScope.launch 函数用于创建并启动一个新的协程,以异步执行一段代码块,不会阻塞当前主线程
// viewModelScope是属于ViewModel的扩展属性,方便在ViewModel中使用协程,并且可以监测Activity或者Fragment的生命周期,然后自动取消,避免内存泄漏
private fun launch(block: suspend () -> Unit, error: suspend (Throwable) -> Unit) = viewModelScope.launch {
try {
block()
} catch (e: Throwable) {
error(e)
}
}
3.怎么理解挂起?
viewModelScope.launch {
var heFengCity = repository.transPortAdCodeToLocationID(areaid)
Toast.makeText(CoolWeatherApplication.context, heFengCity, Toast.LENGTH_SHORT)
}
......
suspend fun transPortAdCodeToLocationID(adcode: String) : HeFengCity = withContext(Dispatchers.IO) {
var city = hefengNetWork.fetchFefengLocation(adcode)
caCheCurrentName(city?.location?.get(0)?.name)
city
}
当执行到transPortAdCodeToLocationID函数的时候,launch中的协程代码就会被切换到另外的IO线程执行,相当于是在当前线程做了一个post的动作,并不会阻塞当前线程的继续执行。当协程代码中的transPortAdCodeToLocationID方法在IO线程执行完之后,会自动切换回之前的线程,并执行toast方法。这样在遇到suspend方法发生线程切换执行,在完成方法执行之后再切换回来原来线程的过程,就叫做挂起。
4.suspend关键字的作用
使用suspend关键字修饰的方法,表示这是一个挂起函数。但仅仅是有suspend,是不能实现挂起的动作的,挂起的动作是由协程内部框架帮我们实现的,即需要直接或者间接调用协程系统内部的suspend方法。比如这里的就调用了withContext方法,它就是协程框架提供的挂起函数。
同时,一个挂起函数,只能在协程代码块中被调用或者被另一个挂起函数调用,最终还是要在协程代码块中被调用,不然就会报错误:
fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
// IDE 报错 Suspend function'withContext' should be called only from a coroutine or another suspend funcion
}
5.什么是非阻塞式挂起
线程都是顺序执行的,我们写的协程代码看上去也是保持的一个上下顺序同步执行的代码块。当执行到挂起函数的时候,整个协程体会从当前线程的执行序列中剥离,并切入到指定的异步任务线程中。此时原来的线程会跳过协程体的代码,并继续顺序执行后面的代码,而不会因为协程体里面有耗时的代码块而阻塞等待。挂起函数在执行完移步任务后,会切回原来的线程,并在恰当的时机继续执行协程体后续的代码。这样看上去是同步顺序执行的代码,但不会影响原线程继续执行的移步任务切换,就叫非阻塞式挂起。如:
main {
GlobalScope.launch(Dispatchers.Main) {
val user = suspendingRequestUser() // 耗时操作
updateView(user)
}
private suspend fun suspendingRequestUser() : User = withContext(Dispatchers.IO) {
api.requestUser()
}
}
6.使用协程做线程的切换
有时候我们只是想把任务放在子线程去处理,在以前可能会用AsysncTask去做,现在可以直接用协程去做线程的转换,相对来说更快捷一些,如下:
// 使用数据库的时候,需要切换子线程
// 使用协程的调度器做线程的转换
CoroutineScope(Dispatchers.IO).launch {
reminderRepository.saveEditContent(databinding.edittext.text.toString())
}