这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
网络请求
现在比较流行的网络框架,就是retrofit,而且retrofit从2.6版本开始,实现了对协程的支持,其实可以理解为retrofit对suspend关键字的支持。 以前如果是使用retrofit来实现网络请求,一般都有这么几个步骤:
- 初始化retrofit
- 初始化Api代理接口
- 请求并在回调中处理结果
private fun initRetrofit() {
val okHttpClient = OkHttpClient.Builder().sslSocketFactory(
TrustAllSSLSocketFactory.newInstance(),
TrustAllSSLSocketFactory.TrustAllCertsManager()
)
retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.build()
api = retrofit.create(GitHubApi::class.java)
}
interface GitHubApi {
@GET("users/{user}/repos")
fun listRepos(@Path("user")user:String):Call<List<Repo>>
}
private fun requestByNormal() {
if (::retrofit.isInitialized && ::api.isInitialized) {
api.listRepos("TonyDash")
.enqueue(object : Callback<List<Repo>> {
override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
textView.text = "requestByNormal onFailure"
}
override fun onResponse(
call: Call<List<Repo>>,
response: Response<List<Repo>>
) {
textView.text = response.body()?.get(0)?.name
}
})
}
}
这样就完成了一次网络请求了。那如果使用协程的话,需要怎么实现呢?其实,在上面的基础上稍作修改,就可以变成协程,初始化retrofit部分不需要改动,先改api接口的定义:
interface GitHubApi {
@GET("users/{user}/repos")
fun listRepos(@Path("user")user:String):Call<List<Repo>>
@GET("users/{user}/repos")
suspend fun listReposKt(@Path("user")user:String):List<Repo>
}
listRepoKt就是支持协程的api,可以看到,区别就只有方法的前面多了一个suspend关键字,suspend的作用就是标记这个为挂起函数。最后来看请求部分:
private fun requestByKt() {
if (::retrofit.isInitialized && ::api.isInitialized) {
GlobalScope.launch(Dispatchers.Main) {
val repos = api.listReposKt("TonyDash")
textView.text ="KT${repos[0].name}"
}
}
}
这样就利用协程完成了一次网络请求了。可以明显看到,在请求数据的部分,少了回调,只需要肉眼看上去的按顺序写,就完成了异线程执行网络请求并主线程更新UI控件的工作,单个请求可能感觉差异不大,但是如果有一个需求你必须请求多个接口,并且多个接口还是有因果关系的,那就会有一种回调地狱的感觉,而且后期维护起来也相对麻烦,万一忘了以前是为什么这么写的呢?还有如果细心的话可以发现,利用回调处理结果的请求方法,有一个onFailure来处理请求的异常情况,那协程呢?协程怎么处理异常,下面我们单独讲。
异常处理
使用try catch来捕捉异常,由于kotlin取消了check exception机制,所以要捕捉异常,我们只能使用try catch来捕捉协程内的异常。
private fun requestByKt() {
if (::retrofit.isInitialized && ::api.isInitialized) {
GlobalScope.launch(Dispatchers.Main) {
try {
val repos = api.listReposKt("TonyDash")
textView.text = "KT${repos[0].name}"
} catch (e: Exception) {
textView.text = e.message ?: "error"
}
}
}
}