(本文代码示例采用Kotlin编写)
1. 前言
此前我们一直是使用RxJava+Retrofit实现网络请求,直接使用这种请求方式是没有和Activity或Fragment的生命周期绑定的,就容易出现内存泄漏或者程序崩溃的情况。因此通常还会加上一个RxlifeCycle来保证和我们的生命周期绑定就变成了RxJava+Retrofit+RxLifeCycle这种结构了,但是自从Android官方推出了LiveData组件,因为LiveData是带有生命周期属性的,我们就可以抛弃原来的写法,来创造属于自己的一套网络请求框架,即下面要讲的LiveData+Retrofit这种模式。
2. RxJava+Retrofit的使用
我们先看下使用RxJava+Retrofit的请求方式是怎么样,
(这里以Github的接口为例)
2.1 准备工作
首先我们会创建一个接口
例如:
interface Api {
//获取账户信息
@GET("users/{account}")
fun getUserDetail(@Path("account") acccount: String?): Observable<GitHubUserBean>
}
这里我们想要的返回结果是这样的 Observable 里面包含了我们想要的返回对象GitHubUserBea
然后我们通过Retrofit来得到这个Api的实例
private val api: Api by lazy {
okHttpClient.addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BODY)
})
val retrofit = Retrofit.Builder().baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(OkHttpClient())
.build()
retrofit.create(Api::class.java)
}
这里baseUrl= https://api.github.com/ ,可以从上面看到最后我们调用的接口地址应该是这样的 https://api.github.com/users/{account},其中account就是我们需要传入的Github账号。 这里我们随便找一个账号吧(JakeWharton)
然后准备工作我们就做好了,现在我们呢就可以请求接口了。 我们一般做法是这样
api.getUserDetail(account).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())?.subscribe(Consumer {user->
Log.i("===>", "$user")
})
这段代码做了网络切换,然后最后输入user信息 我们来先来看看直接在网页上打开请求的结果是不是和我们代码请求的一样
https://api.github.com/users/JakeWharton
然后看看我们执行的代码返回结果
2.2 测试生命周期
上面我们演示了使用Rxjava+Retrofit的基本使用,当然项目中根据我们的数据结构不同,还会有更多层的封装。现在我们来看看之前我们讲到的关于生命周期的问题。我们再写几句代码演示一下。
我们把上面的代码改一下
fun getUserDetail(account: String, consumer: Consumer<GitHubUserBean>) {
api.getUserDetail(account).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())?.subscribe(Consumer { user ->
consumer.accept(user)
})
}
然后添加一个按钮调用这个方法,将返回的数据设置到TextView中
fun aa(view: View) {
GithubRepository.getUserDetail("JakeWharton", Consumer { user ->
txtv.text = user.toString()
})
}
咱们先看下这正常的效果
然后我们测试下异常情况的样子,上面的代码再次修改
请求完成以后 间隔五秒在返回数据
fun getUserDetail(account: String, consumer: Consumer<GitHubUserBean>) {
api.getUserDetail(account).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())?.subscribe(Consumer { user ->
Observable.timer(5, TimeUnit.SECONDS).subscribe {
consumer.accept(user)
}
})
}
得到数据以后,跳转到一个新的activity
fun aa(view: View) {
getUserDetail("JakeWharton", Consumer { user ->
startActivity(Intent(this, SecondActivity::class.java))
})
}
我们的操作是调用这个方法,调用接口,然后返回键退出程序
从上面的图可以看到 退出到桌面以后,过了五秒,自动打开了SecondActivity,这是我们不想看到的,我们希望看到的是退出程序以后,不要执行后续的操作了。
3 LiveData+Retrofit
接下来我们就要进入今天的主题了,LiveData+Retrofit来实现具有生命周期的网络请求
3.1
首先改造上面的接口
interface Api {
@GET("users/{account}")
fun getUserDetailForLiveData(@Path("account") acccount: String?): LiveData<GitHubUserBean>
}
返回的结果我们用LiveData包裹住我们想要的类型即可,这个时候就需要自定义一个属于我们自己的LiveDataCallAdapterFactory类似于RxJava2CallAdapterFactory
3.2 创建自定义转换器
因为Retrofit默认是使用Call返回给我们使用的,如果我们想要返回自己想要的对象就需要创建属于自己的CallAdapter(类似于RxJava2CallAdapterFactory 返回的就是Observable)现在我们就先自定义属于自己的callAdapterFactory,可以仿造系统内部默认的callAdapterFactory来做,这里就不做详细的解释啦,直接贴代码
class LiveDataCallAdapterFactory2 : CallAdapter.Factory() {
private fun LiveDataCallAdapterFactory2() {}
companion object {
fun create() = LiveDataCallAdapterFactory2()
}
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
val rawType = getRawType(returnType)
//关键点1
if (rawType != LiveData::class.java) {
return null
}
if (!(returnType is ParameterizedType)) {
throw IllegalStateException(
"Response must be parametrized as " + "LiveData<Response> or LiveData<? extends Response>"
)
}
val responseType = getParameterUpperBound(0, returnType as ParameterizedType)
//关键点2
if (getRawType(responseType) == GitHubUserBean::class.java) {
return LiveDataAdapter<Any>(
responseType
)
}
return null
}
class LiveDataAdapter<R>(val responseType: Type) : CallAdapter<R, LiveData<GitHubUserBean>> {
override fun adapt(call: Call<R>) = object : LiveData<GitHubUserBean>() {
var started = AtomicBoolean(false)
override fun onActive() {
if (started.compareAndSet(false, true)) {
call.enqueue(object : Callback<R> {
override fun onFailure(call: Call<R>, t: Throwable) {
if (call.isCanceled) return
t.printStackTrace()
postValue(null)
}
override fun onResponse(call: Call<R>, response: retrofit2.Response<R>) {
if (call.isCanceled) return
关键点3
postValue(response.body() as GitHubUserBean)
}
})
}
}
}
override fun responseType() = responseType
}
}
请注意:上面的代码就能用作于返回GitHubUserBean这个对象,想要返回和自己接口定义的数据接口需要改成自己对应的数据接口,一般数据结构为
data class Response<T>(data:T?,code:Int,message:Int)
所以大家想要用到项目中需要再进行对应对象的细微修改即可
3.3 添加自定义转换器
创建好自己的callAdapter之后我们就可以添加到Retrofit中
private val api: Api by lazy {
val addInterceptor =
OkHttpClient().newBuilder().addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BODY)
})
val retrofit = Retrofit.Builder().baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory2.create()) //添加到Retrofit中
.client(addInterceptor.build())
.build()
retrofit.create(Api::class.java)
}
3.4
现在准备工作已经做好了,我们来看看如何调用,以及实现效果
我们点击按钮的时候会调用下面的代码(这里的this是LifecycleOwner,因为是在Activity中调用,所以会持有LifecycleOwner)
Observable.timer(5, TimeUnit.SECONDS).subscribe {
runOnUiThread {
api.getUserDetailForLiveData("JakeWharton").observe(this, Observer { user ->
startActivity(Intent(this, SecondActivity::class.java))
})
}
}
点击按钮,五秒以后会调用接口,然后打开SecondActivity。我们看看这2种情况到底是怎么样
3.4.1 正常不手动退出的情况
上图可以看到,点击按钮以后,等待五秒,调用了接口(调用github api有点不太稳定),自动进入了SecondActivity()
3.4.2 手动退出,需要有生命周期监控的
可以看到,手动退出以后,调用了onStop方法,然后过了五秒,timer五秒结束,提醒改调接口了,但是实际上是没有发送接口调用的。达到了我们预期的效果
5. 结束
这里基本就完结了,RxJava+Retrofit和LiveData+Retrofit的对比本文也已经做了对比,大家可以选择自己感兴趣的做。本次还没有做封装,下次将对LiveData+Retrofit做一次封装。