细说协程零二、为什么要用协程

首先我们来看看目前Android开发中成熟的Retrofit + OkHttp + RxJava网络请求的格式:

fun <V> requestData(mObservable: Observable<BaseData<V>>,callBack: HttpCallBack<V>): Disposable {
       return mObservable.subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(object : Consumer<BaseData<V>> {
               override fun accept(t: BaseData<V>) {
                       if (t != null && t.errorCode == 0) {
                           callBack.success(t.data)
                       } else {
                           callBack.failed(Throwable(t.errorMsg))
                       }
               }

           }, object : Consumer<Throwable> {
               override fun accept(t: Throwable) {
                   Logging.log(t.localizedMessage)
                   if (null != callBack) {
                       callBack.failed(t)
                   } else {
                       throw NullPointerException("the object HttpCallback: callBack is NULL")
                   }
               }
           })
 }

这是我们常规的一个异步请求,通过回调的方式来处理请求结果。

现在我们再来看看kotlin协程下的异步请求:

fun execute01() {
    //切到io线程来执行同步请求
    GlobalScope.launch(Dispatchers.IO) {
        val result = githubApi.getUserSuspend("haoyuegongzi")
        //切到主线程来执行UI操作
        GlobalScope.launch(Dispatchers.Main) {
            println("TAGTAG===>>>IO---result:${Thread.currentThread().name}")
            tvVerifyCode.text = result.toString()
        }
    }
}

interface GitHubApi {
    @GET("users/{login}")
    suspend fun getUserSuspend(@Path("login") login: String): User
}

看到了吗?就这么简单,线程切换,请求数据,线程切换,更新UI。一行代码实现线程切换。
看到这里,或许有人犯嘀咕,我在老的技术组合“Retrofit + OkHttp + RxJava”里面写好回调,连线程切换都直接省了,还需要这么麻烦?

嗯,说得好像有理,我们继续往下看:

    fun execute02() {
        GlobalScope.launch(Dispatchers.Main) {
            //切到子线程执行任务
            var result = withContext(Dispatchers.IO) {
                githubApi.getUserSuspend("haoyuegongzi")
            }
            //任务执行完后自动回到主线程
            println("TAGTAG===>>>IO---result:${Thread.currentThread().name}")
            tvVerifyCode.text = result.toString()
        }
    }

这个withContext函数的意义呢,就是能把耗时任务切到子线程去,任务执行完之后,又会自动切回主线程继续执行。

x线程切换,用什么切?Dispatchers.xxx,这个其实就是告诉编译器这里要执行一个异步代码,调用者需要把我切到协程里:就是这个GlobalScope.launch(Dispatchers.Main/io) 。

到了这里,或许你还会认为,协程目前展现出来的优势,还不足以让你心动到抛弃回调,投身kotlin协程的怀抱。

那我们现在来看看Java/Android开发里面让人抓狂的“地狱回调”:

fun enqueue() {
    //先获取token    
    ApiService.enqueue("/login", object : Callback {
        override fun onResponse(call: Call, response: Response) {
            val token = "..."
            //通过token请求用户信息            
            ApiService.enqueue("/getUserInfo", token, object : Callback {
                override fun onResponse(call: Call, response: Response) {
                    val user = response.body....
                    runOnUiThread {
                        //切换到主线程更新信息                        
                        tvVerifyCode.text = user
                    }
                }

                override fun onFailure(call: Call, e: IOException) {

                }
            })
        }

        override fun onFailure(call: Call, e: IOException) {

        }
    })
}

这里只是两层嵌套,相比开发中遇到过不少这样的情况,有没有很抓狂?
那现在我们来看看用协程怎么实现:

fun execute() {
    GlobalScope.launch(Dispatchers.Main) {
        val token = login()
        val user = githubApi.getUserSuspend(token)
        tvVerifyCode.text = user
    }
}

有没有被惊讶到?震撼到?就这么简单?利用协程的优势就这么明显?

或许你还深深的爱着回调,而且我知道你心里想的是什么,反正用这种多层回调的场景也不多,应用程序能跑起来不影响性能就好了。

这么说确实也没错,较少遇到的开发场景,没必要耗费太多精力。那么我们继续往下看。

前面这种情况,getUserInfo接口是依赖于login接口返回的token的,所以不可避免的使用了回调。

但是现在有一个场景,我们需要将接口A中的接口B中的数据进行合并展示,但是这两个接口在服务端的接口设计上是没有非常强的关联的,这时候出现了两种人。

第一种:想了想觉得没啥办法,然后还是按照先调用接口A,成功后再调用接口B,然后在接口B的回调中进行数据合并

第二种:觉得第一种方式不合理,所以去找服务端“撕逼”,告诉服务端把这两个接口的数据合并到一个接口中返回,客户端处理不了就找服务端呗。这番“撕逼”下来,你有可能成功了,也有可能失败了,但最终的结果都不是非常好。

我们想象一下,假设接口A耗时100ms,接口B耗时120ms,那么实际上在并发处理的情况下,你最快只需要120ms就可以将两个数据进行合并,但是使用嵌套回调的方案那就需要220ms(100ms+120ms)。

之前讲的都只是代码的美观层面的东西,到这里就是性能问题了,各种小的性能问题不解决,一个app怎么可能有比较好的用户体验。s是如何并发实现的:

fun executeAsync() {
    GlobalScope.launch(Dispatchers.Main) {
        //使用async发起两个异步请求
        val one = async { one() }
        val two = async { two() }
        //使用await进行合并
        val result = one.await().toString() + two.await().toString()
        tvVerifyCode.text = result
    }
}

相比上面的嵌套回调,协程的并发请求怎么样?是不是不仅在代码逻辑上简单很多,而且在逻辑上更清晰?代码量更少?

从最后一个例子我们可以知道什么是协程,为什么叫协程:

协程:就是协同多个程序之间进行合作的过程,帮助我们轻松的写出复杂的并发代码,甚至还能用非常简单的方式实现原本不可能实现的并发任务。这就是我们为什么要学习协程的理由。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值