kotlin学习笔记之协程封装回调

协程是什么?

我的理解是它是在单线程的状态下,模拟多线程的效果,去处理一些异步执行的逻辑,实现一些并发的任务的一个东西。暂时把它理解为一个轻量级别的线程。

基础认识

这里只写一些基础知识点。不介绍具体使用方法了。
需要具体使用方法:传送门
我的简单使用归纳如下

几种协程作用域构建器
  • 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()
  • 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)
        }
    }
}
其它基础知识
  • suspend 这个是一个关键字,被它声明的函数将会成为挂起函数,它不会创建一个协程作用域。
  • 父作用域取消会连带子作用域取消,就像树干和树枝,树干被砍了,树枝也没了。
  • …再补充
封装异步逻辑的结果回调

在此之前对于异步信息的处理以及其结果回调我基本都是用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对象.
**/
val result = Service.loadData().await()
val result = Service.loadData().await()
val result = Service.loadData().await()
val result = Service.loadData().await()

如上, 进行多次请求都直接调用 .await() 就行。

你可能感兴趣的:(Android学习)