一、简述
如果说大家对于协程已经有些熟悉了,但是手拿一把未开封的 绝世好剑,却无法发挥真正的威力!或者大家不是很熟悉协程,还不清楚协程所带来的好处是啥?
这篇文章会为大家带来 通窍级别 的体验。
二、示例
2.1 普通写法
以获取一个 View 的宽高为例。
通常我们获取一个 View 的宽高,都是这么做的:
view.post {
val height = view.measuredHeight
val width = view.measuredWidth
}
因为至少需要等到 View 的布局流程走完,我们才能获得 View 的真实宽高。
那么接下来如何用协程来代替这个回调呢?各位请看:
2.2 协程写法
// 1. 创建一个 View 的扩展函数 await()
suspend fun View.await() = suspendCoroutine<View> {
coroutine->
this.post {
// 2. 在 View.post 函数内,将本身返回
coroutine.resume(this)
}
}
使用(为了让大家看出效果,就放截图了):
稍微的解释一下, launch()
是我自己对 GlobalScope.launch()
进行封装后的函数,这里可以看作直接开启了一个协程作用域。之后让 view
调用 await()
函数,返回的值就是宽高确定后的 View 啦。
三、带有成功和失败的回调
3.1 普通写法
我们以获取一个数据的成功或者失败为例:
fun getData(onSuccess:(String)->Unit,onFail:(Throwable)->Unit){
Thread.sleep(1000)
if ((Math.random() *10).toInt() % 2 == 0){
onSuccess("数据")
}else{
onFail(IllegalStateException("获取数据失败"))
}
}
使用:
3.2 协程写法
suspend fun getData() = suspendCoroutine<String> {
Thread.sleep(1000)
if ((Math.random() *10).toInt() % 2 == 0){
it.resume("数据")
}else{
it.resumeWithException(IllegalStateException("获取数据失败"))
}
}
使用:
GlobalScope.launch {
val data = getData()
// <如果能够拿到数据,那就继续往下走>
println("data:${
data}")
}
如果 getData()
能够成功返回数据,那么这样写是没问题的,但是 如果失败了,那就会抛出异常,所以我们必须要使用 try catch
语句:
GlobalScope.launch {
try {
val data = getData()
// <如果能够拿到数据,那就继续往下走>
println("data:${
data}")
}catch (e:Throwable){
// <获取数据失败并且捕获异常>
}
}
对比一下之前的写法……是不是觉得还不如前面的写法呢?所以我们可以利用一下协程上下文——CoroutineConext 来搞搞事情……
3.3 使用协程上下文处理异常
- 我们先创建一个自定义的处理异常的上下文类:
abstract class NetErrorContext:AbstractCoroutineContextElement(NetErrorContext){
companion object Key: CoroutineContext.Key<NetErrorContext>
abstract fun handleException(throwable: Throwable)
}
这是很典型很模板式的写法……
不懂的话也没关系,把这个模板记下来,以后有空的话写写关于协程上下文的文章。
- 然后我们让上下文去处理异常:
suspend fun getData() = suspendCoroutine<String> {
Thread.sleep(1000)
if ((Math.random() *10).toInt() % 2 == 0){
it.resume("数据")
}else{
//it.resumeWithException(IllegalStateException("获取数据失败"))
it.context[NetErrorContext]?.handleException(IllegalStateException("获取数据失败"))
}
}
- 定义一个异常处理对象:
val netErrorHandler = object :NetErrorContext(){
override fun handleException(throwable: Throwable) {
println("网络请求异常……")
}
}
- 最后在开启协程体的时候,将上下文加入:
GlobalScope.launch(context = netErrorHandler) {
val data = getData()
// <如果能够拿到数据,那就继续往下走>
println("data:${
data}")
}
这样的话,如果有异常就会被 netErrorHandler
捕获到。而且又回到这朴实无华的写法了……
当然,你们自己自定义上下文的时候,完全可以根据自己的业务去实现一些方法,上面的例子仅仅是定义了一个 handleException()
方法,并且也只定义了一个参数,在实际开发过程中,这样还是很具有灵活性的……而且协程上下文是可以叠加的……