(网络请求的封装,在本模块中主要是Retrofit和RxJava还有OkHttp的配合。
由于基础知识不够,有些原理并不是太懂,有部分主观的想法,望斧正。)
导读
一、准备
1)创建RetrofitProvider类
2)创建用于网络请求的接口和一个适配于网络请求返回数据的类
二、进行网络请求和返回的数据处理
1)网络请求
2)请求数据的处理
三、这种封装的优点
一、准备
1)准备RetrofitProvider
顾名思义,这是可以为我们提供一个retrofit对象的一个类
Q:为什么要弄这么一个类呢?
A:实现代码复用。
整个项目中的网络请求有一个最基本也最重要的共同点,他们的BASE_URL是一样的。利用这一点设计了这个类,专门为我们提供特定的retrofit。
这个类的代码大概可以分成两部分:构造器和单例。
构造器:
private Retrofit mRetrofit;
private RetrofitProvider() {
...//此处省略一些Interceptor的相关代码
OkHttpClient.Builder clientbuilder = new OkHttpClient.Builder()
// .addInterceptor(loggingInterceptor)
// .addInterceptor(signInterceptor)
// .addNetworkInterceptor(new StethoInterceptor())
// .addInterceptor(new UaInterceptor())
.retryOnConnectionFailure(true)
.connectTimeout(30, TimeUnit.SECONDS);
...
OkHttpClient client = clientbuilder.build();
...//此处省略代理配置的一些代码
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(baseurl)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
mRetrofit = builder.build();
}
可以看到先用OkHttpBuilder.Builder创建了一个retryOnConnectedFailure为true(即网络请求失败了以后再重新请求)和connectTimeout为30seconds(连接默认超时为三十秒)的client。然后再将client传进retrofitBuilder里面去,最后调用builder.build()得到我们需要的retrofit。
省略部分:
HttpLoggingInterceptor:用于记录应用中的网络请求的信息。
SignInterceptor、UaInterceptor:处理host错误问题
还有一些关于代理的代码。
单例
private static class SingletonHolder {
private static final RetrofitProvider INSTANCE = new RetrofitProvider();
}
public static Retrofit getRetrofit() {
return SingletonHolder.INSTANCE.mRetrofit;
}
细心的小伙伴们可能已经发现了,上文中RetrofitProvider的构造器居然是private的!这就是单例模式的一个特点,因为单例类必须创建自己的唯一实例。当一个全局使用的类被频繁的创建和销毁的时候,单例是一种不错的设计模式:当我需要retrofit时,直接调用RetrofitProvider.getRetrofit()即可,而不是在每一次需要它的时候,创建一堆buider再build()。
2)Api接口&适配于请求返回数据的类
接口:
interface GpaApi {
@GET("gpa")
fun get(): Single<Response<GpaBean>>
@FormUrlEncoded
@POST("gpa/evaluate")
fun evaluate(@FieldMap params: Map<String, String>): Single<Response<String>>
}
Single:可以理解成一个没有onComplete的Observable...?
数据类
data class GpaBean(val stat: Stat, val data: List<Term>, val updated_at: String, val session: String)
....
与网络请求返回的值的名称和类型要匹配上
二、网络请求和数据处理
1)GPAProvider
我们只看其中的一段代码,里面主要有一个updateGpaLiveData的函数
private val api = RetrofitProvider.getRetrofit().create(GpaApi::class.java)
...
fun updateGpaLiveData(useCache: Boolean = true, silent: Boolean = false) {
async(UI) {
val remote = bg {
api.get().toBlocking().value()?.data
}
if (useCache) {
bg {
Hawk.get<GpaBean?>(HAWK_KEY_GPA, null)
}.await()?.let {
gpaLiveData.value = it
if (!silent) successLiveData.value = ConsumableMessage("从缓存中拿到了你的 GPA")
}
}
remote.await()?.let {
if (it.updated_at != gpaLiveData.value?.updated_at) {
gpaLiveData.value = it
if (!silent) successLiveData.value = ConsumableMessage("你的 GPA 有更新喔")
Hawk.put<GpaBean>(HAWK_KEY_GPA, it)
} else {
if (!silent) successLiveData.value = ConsumableMessage("你的 GPA 已经是最新的了")
}
CommonPrefUtil.setGpaToken(it.session)
}
}.invokeOnCompletion {
it?.let {
errorLiveData.value = ConsumableMessage("好像出了什么问题,${it.message}")
}
}
}
先用retrofit创建api
private val api = RetrofitProvider.getRetrofit().create(GpaApi::class.java)
再调用api的@GET方法
api.get()
我们就可以得到网络请求的返回值了!不过呢
哪有这么简单。。。
往前翻一下你就会看见emmm我们get()函数的返回值是Single<Response<GpaBean>>,所以我们要给它去个壳,拿出我们需要的GpaBean.data
api.get().toBlocking().value()?.data
嗯,还没完
对于网络请求这种耗时的工作,我们不能让它在主线程进行,不然会引起阻塞然后app崩掉.....
这时候kotlin的协程(coroutines)就出马了
咳咳咳不是这个。在下面~
async(UI) {
val remote = bg {
api.get().toBlocking().value()?.data
}
...
remote.await()?.let {
...//数据的处理
}
...
}
}
async(UI)表示在UI线程中进行,remote = bg{}
个人理解是bg后面代码块里面(一些耗时操作)的东西在后台运行。
remote.await()是等操作结束后返回的值,这里呢就是我们想要的data。
但是!
但是!
但是!
还是没有完有些时候网络请求出问题了怎么办???我们返回来的是空的值怎么办???
调用
.invokeOnCompletion {
it?.let {
errorLiveData.value = ConsumableMessage("好像出了什么问题,${it.message}")
}
}
对崩了的网络请求进行一个处理,嘻嘻好像还不算太难~
这么一来,对于网络请求的发起和数据处理就差不多了
等等,差不多了???说好的RxJava呢???
于是我翻来翻去翻到了这样一个文件:LiveDataExtensions.kt。。。
fun <T, U> LiveData<T>.map(func: (T) -> U): LiveData<U> = Transformations.map(this, func)
fun <T, U> LiveData<T>.switchMap(func: (T) -> LiveData<U>): LiveData<U> = Transformations.switchMap(this, func)
fun <T> LiveData<T>.bind(lifecycleOwner: LifecycleOwner, block: (T?) -> Unit) = observe(lifecycleOwner, Observer(block))
fun <T> LiveData<ConsumableMessage<T>>.consume(lifecycleOwner: LifecycleOwner, from: Int = ConsumableMessage.ANY, block: (T?) -> Unit) =
observe(lifecycleOwner, Observer {
if (it?.consumed == false && (ConsumableMessage.ANY == from || it.from == from)) {
block(it.message)
it.consumed = true
}
})
data class ConsumableMessage<out T>(val message: T, val from: Int = ANY, var consumed: Boolean = false) {
companion object {
const val ANY = -1
}
}
看了一下LiveData的源码注释,LiveData is a data holder class that can be observed within a given lifecycle。我的理解是:当我们给它一个状态为ACTIVE的LifeCycleOwner的时候,它就是一个Observable,而上面一系列的函数是LiveData的拓展函数。
//语法糖
扩展函数:在不修改原来类的条件下,使用函数和属性,表现的就像是属于这个类一样,很神奇。以第一个为例,这里对于LiveData进行函数拓展,设置LiveData的map函数为Transformation的map函数,实现流的映射关系。
又顺蔓摸瓜爬到了这里~
GpaProvider.gpaLiveData
.map(GpaBean::stat)
.map(Stat::total)
.bind(lifecycleOwner) {
it?.let {
scoreTv.text = it.score.toString()
gpaTv.text = it.gpa.toString()
creditTv.text = it.credit.toString()
}
}
gpaLiveData是我们用前面处理后的List<GPABean>.data,对于data我们要做进一步的处理。因为我们只需要data.stat.total,可以用.map()函数将流中的内容一一映射,最后用.bind()函数绑定一个lifecycleOwner(相当于onsubscribe()?),然后it.let{}(设置onNext()?)来更新view的内容(这个我只是感觉像RxJava,和我初步了解的有些不同。典型的流式结构,没有切换线程是因为没有耗时的操作...由于订阅关系,当网络请求返回的数据改变,view的内容也会变。。。但是刚接触RxJava对这里了解的不够,这个是一个猜测(溜))
也算是用到了RxJava? ( 我继续学习去了再见~)
更:emmmm在tjuLibrary里面找到了典型的RxJava
public void bindLibrary(Action1<Integer> action1, String libpasswd) {
libApi.bindLib(libpasswd)
.map(ApiResponse::getData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> action1.call(-1), new RxErrorHandler(mContext));
}
在io线程中,libApi调用了bindlib方法后由网络请求返回一个ApiResponse类的对象,然后对它调用.map(ApiResponse::getData)方法映射成为apiResponse.data。此时已完成耗时的操作,调回主线程,实现subscribe订阅关系(onNext:传进来的action,onError:new RxErrorHandler)
三、这样封装的优点
1)Retrofit可以配置client来实现网络请求,且请求的方法和参数注解都可以定制。
2)由于RxJava是流式结构链式调用,思路很清晰。
3)Retrofit负责网络请求和请求数据的粗略处理,RxJava负责数据的精密处理和UI更新。Retrofit和async(UI)结合优雅的处理了异步处理网络请求的问题,RxJava的Observable和Subscriber之间的subscribe很好的满足了view——“我监测到了某一方面的(显示的数据)变化,我要更新自己” 的需求
网友评论