前言
这里只写一些基础知识小结。不介绍具体使用方法了。
需要具体使用方法:传送门
几种协程作用域构建器
-
runBlocking{}
它的特点是会一直阻塞当前线程(不一定是主线程),直到该作用域下所用逻辑执行完毕。 -
GlobalScope.launch{}
它的特点创建的是顶层协程,没处理好会造成没必要的资源消耗。
比如你在在一个Activity中使用它创建一个协程执行耗时的逻辑,然而在Activity已经finish还没有进行回调,那么就会产生一些额外的资源消耗。 -
launch{}
它只能在协程作用域下使用,也就是说它只创建子作用域 -
async{}
和launch{}一样它也是只能创建子作用域,不过它的特殊之处在于,该函数会返回一个 Deferred 对象,调用该对象的 .await()函数可获取async{}函数代码块的执行结果
此时如果代码块还没有执行完毕,则一直阻塞当前协程,等待结果返回。 -
withContext(Dispatchers.IO){}。
这个函数的和 async{}.await() 的效果一样的,同样也会阻塞当前协程,直到协程逻辑执行完毕返回数据,不一样的是这个函数必须用调度器指定协程运行在哪个线程。
调度器一般有三种:Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新UI 。 Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行。通常用于处理高并发的逻辑,比如网络请求。 Dispatchers.Default:这个调度器经过优化,可以在主线程之外执行 cpu 密集型的工作。例如对列表进行排序和解析 JSON。在线程池中执行。
-
coroutineScope{} 它有三个特点:
1、只能在挂起函数和协程作用域下使用。
2、它提供的作用域是继承于父作用域的。
3、它会一直阻塞当前协程,注意是“当前的协程”,而不会阻塞线程和其它协程 -
CoroutineScope()
这个函数(尽管命名像一个类)的名字和上面的就差首字母大小写,注意不要混淆。
它接受一个参数是线程的上下文,返回一个CoroutineScope对象。可以该对象使用函数 launch{}和async{} 创建协程。通常配合和 Job 一起使用,实例代码:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
}
job.cancel()
其它
suspend
- 这个是一个关键字,被它声明的函数将会成为挂起函数,它不会创建一个协程作用域。
suspendCoroutine
- 它只能在挂起函数或协程作用域中使用。
- 它接收一个带协程上下文参数的Lambda表达式。可用这个协程上下文调用resume()和resumeWithException(e) 恢复挂起的函数。
suspend fun test(): Int{
val i = 2
return suspendCoroutine {
if(i<5){
it.resume(i)
}else{
val e = Exception()
it.resumeWithException(e)
}
}
}
异步逻辑执行结果封装
在此之前对于异步信息的处理以及其结果回调我基本都是用RxJava, 但如果RxJava的功能是很强大的,如果仅仅是对于一些简单的异步信息回调,也没必要用到这个大家伙。了解了上面的信息后可以用很少的代码就封装一个自己的回调函数。
以Room的数据查询为例。先看一个Dao层函数:
@Query("select * from diary where is_abandon = :id")
fun loadDiaries(id: Int): List<Diary>
它的作用是查询数据库一个字段的信息,以List<>对象返回数据。但总所周知Room操作数据库是不允许直接在UI线程调用的。解决办法:
- 我们可以手动 new 一个Thread执行,然后再切回主线程更新UI;
- 更常规操作是直接用RxJava来切换线程,处理该逻辑。
然而现在用协程可以用很少的代码就可以封装一个函数完成这个需求。
private suspend fun <T> execute(block: () -> T): T{
return withContext(Dispatchers.IO) {
block()
}
}
就是这么简短的代码就封装好了。如果了解上面的知识,相信这段代码还是很易懂的。
execute() 接受一个lambda表达式,我们可以把异步的逻辑传入,withContext(){} 使用调度器使得逻辑在IO线程执行,执行结果在被调用的线程返回,我们在主线程调用那么就是在主线程返回数据。
比如用封装好的 execute() 调用刚才那个Dao层的 loadDiaries() 函数查询id为2的数据,此时该函数将会在IO线程执行,结果List<>在主线程返回。
suspend fun loadDiaries() = execute{
diaryDao.loadDiaries(2)
}
可能要问,如果返回的结果出错怎么办?协程出现的异常都会被抛出,只要在外面捕获一下就行了。一上面为例, 在协程中查询数据库信息并更新UI:
scope.launch {
try {
val result = loadDiaries()
//更新UI
}catch (e: Exception){
e.message?.logD()
//数据库查询失败的逻辑
}
}
带回调的结果封装
很多时候,对于异步操作, 所用的库都会给你封装好了.通常类似于这样的结构
Call<A>.execute(object: Listener<A>(){
onSuccess(a: A){
}
onFail(e: Exception){
}
})
这样封装有一点苦恼, 每次使用 Call.execute() 都要这样写一遍, 比如网络请求,每请求一个就写一次回调。
那么可以用协程来进一步封装使得该函数只写一遍就行.
suspend fun <T>Call<T>.await(): T{
return suspendCoroutine {
execute(object: Listener(){
onSuccess(t: T){
it.resume(T)
}
onFail(e: Exception){
it.resumeWithException(e)
}
})
}
}
写一个Call的 拓展函数, 使用 suspendCoroutine{} 将请求挂起,当接受到结果时恢复。
封装好后以后无论进行多少次请求,都只写一此回调函数就行了。
/**
这里假设Service.loadData()会返回一个Call<T>对象.
**/
val result = Service.loadData().await()
val result = Service.loadData().await()
val result = Service.loadData().await()
val result = Service.loadData().await()
如上, 进行多次请求都直接调用 .await() 就行。