该篇基于Kotlin 协程学习,国庆放假来巩固一下知识。
Retrofit更新到 2.6.0
的版本后,引入了对协程的支持,这样更利于我们打造优雅的网络框架。但是现在大部分Android框架都用 RxJava + Retrofit
的,而协程本身是和RxJava功能重合的(便于切线程),所以我需要理清这两者的区别,在使用时合理取舍。
1. Retrofit对协程的支持
2.6.0
版本,Retrofit可以使用协程来进行网络请求。我们先看看前后有什么区别。
1.1 原始的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()
的成功失败回调就行了。
1.2 使用协程的Retrofit
协程可以帮我们解决掉写回调的麻烦,所以我们可以把请求写在协程中,这也需要我们调用的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
}
}
这就比较难受了…
2. 协程与RxJava
2.1 代码的比较
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}")
}
}
2.2 总结
他们的共同点:
- 都可以切换线程
- 都不需要嵌套
他们的不同点
- 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。
3. 协程的“泄漏”
在Kotlin 协程学习的第四节中,讲到了如何去取消一个协程,为什么要取消没有写的很明白,我们让协程跑完不就行了?
在Android日常的开发中,我们是不知道用户会做什么事情的,假如当前程序正在执行一个协程来执行一个耗时的任务,而用户在任务还没有完成的时候把页面关了(就是会走 onDestroy()
那种),那我们的协程也需要关闭才对,不然的话,协程可是会跑一个线程的,它继续跑下去的,GC就会受不了,导致产生“内存泄漏”。因为 一个线程就是一个 GC Root
。
所以在面试中, 面试官所说的 “协程泄漏”其实就等于 “线程的内存泄漏”,它本质上和 AsyncTask
的泄漏是样的。