美文网首页
WeiPeiYang阅读笔记(2)GPA2&tjuLibrary

WeiPeiYang阅读笔记(2)GPA2&tjuLibrary

作者: 毒死预言家的女巫 | 来源:发表于2018-02-09 21:23 被阅读0次

(网络请求的封装,在本模块中主要是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——“我监测到了某一方面的(显示的数据)变化,我要更新自己” 的需求

相关文章

网友评论

      本文标题:WeiPeiYang阅读笔记(2)GPA2&tjuLibrary

      本文链接:https://www.haomeiwen.com/subject/kavftftx.html