WeiPeiYang阅读笔记(2)GPA2&tjuLibrary网络请求的封装

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,128评论 25 707
  • 懒得处理样式了, 将就着看吧. 官网地址: https://developer.android.com/topic...
    Reddington_604e阅读 1,629评论 0 1
  • 相信很多人都在使用Retrofit,我也在用,但是对它的理解都不是太深刻,现在Retrofit2已经出来一段时间,...
    WHOKNOWME阅读 7,436评论 6 19
  • 或打工或读书,家里人都外出了,大家都反对老母亲一个人留在老家,没办法她还是同意到县城和我一起生活。 离开老家,...
    快来看哥阅读 361评论 0 0
  • 一见如故的登山照 明年今日的第二次 远方电影般的蓝天雪山小红瓦的房子
    Andorra阅读 103评论 0 0