该篇基于Kotlin 协程学习,国庆放假来巩固一下知识。
Retrofit更新到 2.6.0的版本后,引入了对协程的支持,这样更利于我们打造优雅的网络框架。但是现在大部分Android框架都用 RxJava + Retrofit的,而协程本身是和RxJava功能重合的(便于切线程),所以我需要理清这两者的区别,在使用时合理取舍。
2.6.0版本,Retrofit可以使用协程来进行网络请求。我们先看看前后有什么区别。
Api定义如下:
interface Api { @GET("user/{user}/repos") fun listRepos(@Path("user") user: String): Call<ResponseBody> }接着在代码中使用:
val apiService = Retrofit.Builder() .baseUrl("https://api.github.com/") .build().create(Api::class.java) apiService.listRepos("Rikkatheworld").enqueue(object : Callback<ResponseBody> { override fun onFailure(call: Call<ResponseBody>?, t: Throwable?) { textview.text = t?.message } override fun onResponse(call: Call<ResponseBody>?, response: Response<ResponseBody>?) { textview.text = call } }) }老版本的Retrofit需要在构建请求时,声明函数类型为 Call,这样可以回调到主线程,接着只要在回调方法中实现 onResponse()和 onFailure()的成功失败回调就行了。
协程可以帮我们解决掉写回调的麻烦,所以我们可以把请求写在协程中,这也需要我们调用的Api函数需要被 @suspend修饰
interface Api { @GET("user/{user}/repos") suspeend fun listRepos(@Path("user") user: String): ResponseBody }接着在代码中开一个协程来消除回调:
GlobalScope.launch(Dispatchers.Main) { // 声明为主线程的协程 val repos = apiService.listRepos("Rikkatheworld") // 在子线程进行网络请求 textview.text = repos.toString() // 回到主线程更新ui }这样来看,就真的消除了回调。但是有一个问题:上面代码的 repos是请求成功后的结果,那么失败怎么办? 目前Kotlin和Retrofit都没有给出方案,所以只能采用 try-catch的方法
GlobalScope.launch(Dispatchers.Main) { try { val repos = apiService.listRepos("Rikkatheworld") textview.text = repos.toString() } catch (e: Exception) { textview.text = e.message } }这就比较难受了…
RxJava的代码大家都写惯了,大概是这样的:
apiService.listRepos("Rikkatheworld") //将请求放在后台 .subscribeOn(Schedulers.io()) //将结果的返回放在前台 .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : SingleObserver<List<Repo>>() { fun onSubscribe(d: Disposable) { Log.d(TAG, "onSubscribe") } fun onSuccess(repo: List<Repo>) { Log.d(TAG, repo[0].toString()) } fun onError(e: Throwable) { Log.e(TAG, e.message) } })而使用协程,则可以使用 async来切线程:
GlobalScope.launch(Dispatchers.Main) { try { val res = async { apiService.listRepos("Rikkathewrold") } //异步获取挂起结果 textview.text = res.await().toString() //主线程更新ui } catch (e: Exception) { Log.e(TAG, "${e.message}") } }他们的共同点:
都可以切换线程都不需要嵌套他们的不同点
RxJava需要写回调,而协程不需要RxJava链式调用,结构清晰;而协程会自己切换线程,所以代码更加简洁RxJava和协程的功能和使用场景基本一致,而协程在结构上比RxJava更加简单,如果使用数据流,协程还可以使用 Flow,可以学下这篇文章:Kotlin协程(5)Flow。
而性能上,在 RxJava vs. Kotlin Coroutines, a quick look的文章中进行了对比,因为协程在处理挂起函数的逻辑上较为复杂,所以在性能上 协程比RxJava更弱一点。 但是这是2017年的文章了,现在不知道Kotlin官方有没有进行改进。
我个人认为,以后Android开发都是Jetpack全家桶了,而Kotlin团队JetBrains又是Google“亲儿子”,所以以后Jetpack肯定会更加支持协程的, 所以我更相信KT官方会解决协程的问题,而更偏向在代码中使用协程去取代RxJava。
在Kotlin 协程学习的第四节中,讲到了如何去取消一个协程,为什么要取消没有写的很明白,我们让协程跑完不就行了?
在Android日常的开发中,我们是不知道用户会做什么事情的,假如当前程序正在执行一个协程来执行一个耗时的任务,而用户在任务还没有完成的时候把页面关了(就是会走 onDestroy()那种),那我们的协程也需要关闭才对,不然的话,协程可是会跑一个线程的,它继续跑下去的,GC就会受不了,导致产生“内存泄漏”。因为 一个线程就是一个 GC Root。
所以在面试中, 面试官所说的 “协程泄漏”其实就等于 “线程的内存泄漏”,它本质上和 AsyncTask的泄漏是样的。
