Alec's blog

唯纯粹与热爱不可辜负

0%

使用Coroutines封装回调

前言

RxJava执行异步任务执行结果通常以回调的方式返回,当存在多个异步任务时,返回的多个结果难以协作。而使用Courtines则可以将异步执行的结果返回到同一个代码块里面,这将大大提高了多个异步执行任务的协作能力。

suspendCoroutine

  • 它只能在挂起函数或Coroutines作用域中使用。
  • 它接收一个带Coroutines上下文参数的Lambda表达式。可用这个Coroutines上下文调用resume()和resumeWithException(e) 恢复挂起的函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    suspend fun test(): Int{
    val i = 2
    return suspendCoroutine {
    if(i<5){
    it.resume(i)
    }else{
    val e = Exception()
    it.resumeWithException(e)
    }
    }
    }

异步执行结果封装

以Room的数据查询为例。先看一个Dao层函数:

1
2
@Query("select * from diary where id = :id")
fun loadDiary(id: Int): Diary

它的作用是查询数据库一个字段的信息,以Diary对象返回数据。但总所周知Room操作数据库是不允许直接在UI线程调用的。解决办法:

  • 我们可以手动 new 一个Thread执行,然后再切回主线程更新UI;
  • 更常规操作是直接用RxJava来切换线程,处理该逻辑。

然而现在用Coroutines可以用很少的代码就可以封装一个函数完成这个需求。

1
2
3
4
5
private suspend fun <T> execute(block: () -> T): T{
return withContext(Dispatchers.IO) {
block()
}
}

execute() 接受一个lambda表达式,我们可以把异步的逻辑传入,withContext(){} 使用调度器使得逻辑在IO线程执行,执行结果在被调用的线程返回,我们在主线程调用那么就是在主线程返回数据。

用封装好的 execute() 调用刚才那个Dao层的 loadDiaries() 函数查询id为1和id为2 生成一个list返回即可。

1
2
3
suspend fun loadDiaries() = execute{
listOf<Diary>(diaryDao.loadDiary(1), diaryDao.loadDiary(2))
}

像这样将两个异步任务结果合并在一起采用回调的方式很难做到。

出现异常时Coroutines会将其抛出,可以使用try-catch捕获。

1
2
3
4
5
6
7
8
9
scope.launch {
try {
val result = loadDiaries()
//更新UI
}catch (e: Exception){
e.message?.logD()
//数据库查询失败的逻辑
}
}

带回调的结果封装

很多时候对于异步操作,所用的库都会用回调给你封装好了。通常类似于这样的结构

1
2
3
4
5
6
Call<A>.execute(object: Listener<A>(){
onSuccess(a: A){
}
onFail(e: Exception){
}
})

想要摆脱回调带来的苦恼,可以用Coroutines来进一步封装。

1
2
3
4
5
6
7
8
9
10
11
12
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{} 将请求挂起,当接受到结果时恢复。

封装好后以后无论进行多少次请求,都只写一此回调函数就行了。

1
2
3
4
5
6
7
/**
这里假设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() 就行。