深入浅出RxJava+Retrofit+OkHttp网络请求
浅谈rxjava+retrofit+okhttp 封装使用 之前发出后收到很多朋友的关注,原本只是自己学习后的一些经验总结,但是有同学运用到实战当中,这让我很惶恐,所有后续一直更新了很多次版本,有些地方难免有所变动导致之前的博客有所出入,正好最近受到掘金邀请内测博客,所以决定重新写一版,按照最后迭代完成的封装详细的讲述一遍,欢迎大家关注!
注意:由于本章的特殊性,后续文章比较长而且复杂,涉及内容也很多,所以大家准备好茶水,前方高能预警。
简介:
retrofit: retrofit是square 公司开发的一款正对android 网络请求的框架。底层基于okhttp 实现,okhttp 已经得到了google 官方的认可。
okhttp: 也是square 开源的网络请求库
rxjava:rxjava 在 github 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the java vm"(一个在 java vm 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 rxjava ,概括得非常精准。总之就是让异步操作变得非常简单。
各自的职责:retrofit 负责请求的数据和请求的结果,使用接口的方式呈现,okhttp 负责请求的过程,rxjava 负责异步,各种线程之间的切换。
rxjava + retrofit + okhttp 已成为当前android 网络请求最流行的方式。
封装成果
封装完以后,具有如下功能:
1.retrofit+rxjava+okhttp基本使用方法
2.统一处理请求数据格式
3.统一的progressdialog和回调subscriber处理
4.取消http请求
5.预处理http请求
6.返回数据的统一判断
7.失败后的retry封装处理
8.rxlifecycle管理生命周期,防止泄露
实现效果:
具体使用
封装后http请求代码如下
// 完美封装简化版 private void simpledo() { subjectpost postentity = new subjectpost(simpleonnextlistener,this); postentity.setall(true); httpmanager manager = httpmanager.getinstance(); manager.dohttpdeal(postentity); } // 回调一一对应 httponnextlistener simpleonnextlistener = new httponnextlistener<list<subject>>() { @override public void onnext(list<subject> subjects) { tvmsg.settext("已封装:\n" + subjects.tostring()); } /*用户主动调用,默认是不需要覆写该方法*/ @override public void onerror(throwable e) { super.onerror(e); tvmsg.settext("失败:\n" + e.tostring()); } };
是不是很简单?你可能说这还简单,好咱们对比一下正常使用retrofit的方法
/** * retrofit加入rxjava实现http请求 */ private void onbutton9click() { //手动创建一个okhttpclient并设置超时时间 okhttp3.okhttpclient.builder builder = new okhttpclient.builder(); builder.connecttimeout(5, timeunit.seconds); retrofit retrofit = new retrofit.builder() .client(builder.build()) .addconverterfactory(gsonconverterfactory.create()) .addcalladapterfactory(rxjavacalladapterfactory.create()) .baseurl(httpmanager.base_url) .build(); / 加载框 final progressdialog pd = new progressdialog(this); httpservice apiservice = retrofit.create(httpservice.class); observable<retrofitentity> observable = apiservice.getallvedioby(true); observable.subscribeon(schedulers.io()).unsubscribeon(schedulers.io()).observeon(androidschedulers.mainthread()) .subscribe( new subscriber<retrofitentity>() { @override public void oncompleted() { if (pd != null && pd.isshowing()) { pd.dismiss(); } } @override public void onerror(throwable e) { if (pd != null && pd.isshowing()) { pd.dismiss(); } } @override public void onnext(retrofitentity retrofitentity) { tvmsg.settext("无封装:\n" + retrofitentity.getdata().tostring()); } @override public void onstart() { super.onstart(); pd.show(); } } ); }
可能你发现确是代码有点多,但是更加可怕的是,如果你一个activity或者fragment中多次需要http请求,你需要多次重复的写回调处理(一个回到就有4个方法呀!!!!反正我是忍受不了),而且以上处理还没有做过多的判断和错误校验就如此复杂!~好了介绍完了,开始咱们的优化之路吧!
项目结构:
retrofit
咱家今天的主角来了,咱们也深入浅出一下了解下retrofit使用,前方高能,如果你是深度retrofit选手请直接跳过本节!!!
1.首先确保在androidmanifest.xml中请求了网络权限
<uses-permission android:name="android.permission.internet"/>
2.在app/build.gradle添加引用
/*rx-android-java*/ compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'com.trello:rxlifecycle:1.0' compile 'com.trello:rxlifecycle-components:1.0' /*rotrofit*/ compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.0.0' compile 'com.google.code.gson:gson:2.8.0'
3.常用注解
这里介绍一些常用的注解的使用
- @query、@querymap:用于http get请求传递参数
- @field:用于post方式传递参数,需要在请求接口方法上添加@formurlencoded,即以表单的方式传递参数
- @body:用于post,根据转换方式将实例对象转化为对应字符串传递参数.比如retrofit添加gsonconverterfactory则是将body转化为gson字符串进行传递
- @path:用于url上占位符
- @part:配合@multipart使用,一般用于文件上传
- @header:添加http header
- @headers:跟@header作用一样,只是使用方式不一样,@header是作为请求方法的参数传入,@headers是以固定方式直接添加到请求方法上
retrofit基本使用:
首先给定一个测试接口文档,后面的博客中我们都是用这个接口调试
/** * @api videolink 50音图视频链接 * @url http://www.izaodao.com/api/appfiftytonegraph/videolink * @method post * @param once_no bool(选填,ture无链接) 一次性获取下载地址 * @return json array( * ret:1成功,2失败 * msg:信息 * data:{ * name:视频名称 * title:标题 * } )
1.初始化retrofit
要向一个api发送我们的网络请求 ,我们需要使用retrofit builder类并指定service的base url(通常情况下就是域名)。
string base_url = " http://www.izaodao.com/api/" retrofit retrofit = new retrofit.builder() .baseurl(base_url) .addconverterfactory(gsonconverterfactory.create()) .build();
2.设置接口service
注意到每个endpoint 都指定了一个关于http(get, post, 等等。) 方法的注解以及用于分发网络调用的方法。而且这些方法的参数也可以有特殊的注解。
/** * 接口地址 * created by wzg on 2016/7/16. */ public interface myapiendpointinterface { @post("appfiftytonegraph/videolink") call<retrofitentity> getallvedio(@body boolean once_no) }
3.得到call然后同步处理处理回调:
myapiendpointinterface apiservice = retrofit.create(myapiendpointinterface.class); call<retrofitentity> call = apiservice.getallvedio(true); call.enqueue(new callback<retrofitentity>() { @override public void onresponse(response<retrofitentity> response, retrofit retrofit) { retrofitentity entity = response.body(); log.i("tag", "onresponse----->" + entity.getmsg()); } @override public void onfailure(throwable t) { log.i("tag", "onfailure----->" + t.tostring()); } });
这就是简单的retrofit使用步骤,接下来我们结合rxjava讲述
retrofit+rxjava基本使用
对比之前的retrofit使用
1.在于我们需要修改service接口返回信息我们需要返回一个observable对象
@post("appfiftytonegraph/videolink") observable<retrofitentity> getallvedioby(@body boolean once_no);
2.然后初始化retrofit需要添加对rxjava的适配,注意一定要retrofit2才有这个功能哦
retrofit retrofit = new retrofit.builder() .client(builder.build()) .addconverterfactory(gsonconverterfactory.create()) .addcalladapterfactory(rxjavacalladapterfactory.create()) .baseurl(httpmanager.base_url) .build();
3.回调通过rxjava处理
httpservice apiservice = retrofit.create(httpservice.class); observable<retrofitentity> observable = apiservice.getallvedioby(true); observable.subscribeon(schedulers.io()).unsubscribeon(schedulers.io()).observeon(androidschedulers.mainthread()) .subscribe( new subscriber<retrofitentity>() { @override public void oncompleted() { } @override public void onerror(throwable e) { } @override public void onnext(retrofitentity retrofitentity) { tvmsg.settext("无封装:\n" + retrofitentity.getdata().tostring()); } } );
简单的rxjava集合retrofit的使用就介绍完了,同样的可以发现使用起来很多重复性的代码,而且使用也不是那么简单,所以才有了下面的封装
retrofit+rxjava进阶封装之路
先来一张流程图压压惊
请求数据封装
1.参数
首先需要封装的使我们的数据类,在数据类中需要封装请求中用到的相关数据的设置,比如请求参数、方法、加载框显示设置等等
public abstract class baseapi<t> implements func1<baseresultentity<t>, t> { //rx生命周期管理 private softreference<rxappcompatactivity> rxappcompatactivity; /*回调*/ private softreference<httponnextlistener> listener; /*是否能取消加载框*/ private boolean cancel; /*是否显示加载框*/ private boolean showprogress; /*是否需要缓存处理*/ private boolean cache; /*基础url*/ private string baseurl="http://www.izaodao.com/api/"; /*方法-如果需要缓存必须设置这个参数;不需要不用設置*/ private string mothed; /*超时时间-默认6秒*/ private int connectiontime = 6; /*有网情况下的本地缓存时间默认60秒*/ private int cookienetworktime=60; /*无网络的情况下本地缓存时间默认30天*/ private int cookienonetworktime=24*60*60*30; }
注释很详细,这里不具体描述了,由于这里是最后封装完成以后的代码,所以有些内容本章还会部分不会涉及,因为功能太多,还是按照一开始的博客章节讲解。
2.抽象api接口
/** * 设置参数 * * @param retrofit * @return */ public abstract observable getobservable(retrofit retrofit);
通过子类也即是我们的具体api接口,通过getobservable实现service中定义的接口方法,例如:
public class subjectpostapi extends baseapi { xxxxxxx xxxxxxx @override public observable getobservable(retrofit retrofit) { httppostservice service = retrofit.create(httppostservice.class); return service.getallvediobys(isall()); } }
通过传入的retrofit对象,可以随意切换挑选service对象,得到定义的注解方法,初始完成以后返回observable对象。
3.结果判断
这里结合rxjava的map方法在服务器返回数据中,统一处理数据处理,所以baseapi<t> implements
func1<baseresultentity<t>, t>,后边结合结果处理链接起来使用 @override public t call(baseresultentity<t> httpresult) { if (httpresult.getret() == 0) { throw new httptimeexception(httpresult.getmsg()); } return httpresult.getdata(); }
由于测试接口,也是当前我们公司接口都是有统一规则的,想必大家都有这样的接口规则,所以才有这里的统一判断,规则如下:
* ret:1成功,2失败 * msg:信息 * data:{ * name:视频名称 * title:标题 * }
其实上面的接口文档中就介绍了,统一先通过ret判断,失败显示msg信息,data是成功后的数据也就是用户关心的数据,所以可封装一个结果对象baseresultentity.
4.结果数据
/** * 回调信息统一封装类 * created by wzg on 2016/7/16. */ public class baseresultentity<t> { // 判断标示 private int ret; // 提示信息 private string msg; //显示数据(用户需要关心的数据) private t data; xxxxx get-set xxxxx }
这里结合baseapi的func1判断,失败直接抛出一个异常,交个rxjava的onerror处理,成功则将用户关心的数据传给gson解析返回
5.泛型传递
baseresultentity<t>中的泛型t也就是我们所关心的回调数据,同样也是gson最后解析返回的数据,传递的过程根节点是通过定义service方法是给定的,例如:
public interface httppostservice { @post("appfiftytonegraph/videolink") call<retrofitentity> getallvedio(@body boolean once_no); }
其中的retrofitentity就是用户关心的数据类,通过泛型传递给最后的接口。
6.强调
很多兄弟通过qq群反馈给我说,使用一个接口需要写一个对应的api类继承baseapi是不是很麻烦,我这里强调一下,这样封装是为了将一个api接口作为一个对象去封装,个人觉得有必要封装成一个类,在日后工程日益增加接口随着增加的同时,对象的做法更加有利于查找接口和修改接口有利于迭代。
操作类封装
1初始对象
首先初始化一个单利方便httpmanager请求;这里用了volatile的对象
private volatile static httpmanager instance; //构造方法私有 private httpmanager() { } //获取单例 public static httpmanager getinstance() { if (instance == null) { synchronized (httpmanager.class) { if (instance == null) { instance = new httpmanager(); } } } return instance; }
2接口处理和回调处理:
/** * 处理http请求 * * @param basepar 封装的请求数据 */ public void dohttpdeal(baseapi basepar) { //手动创建一个okhttpclient并设置超时时间缓存等设置 okhttpclient.builder builder = new okhttpclient.builder(); builder.connecttimeout(basepar.getconnectiontime(), timeunit.seconds); builder.addinterceptor(new cookieinterceptor(basepar.iscache())); /*创建retrofit对象*/ retrofit retrofit = new retrofit.builder() .client(builder.build()) .addconverterfactory(gsonconverterfactory.create()) .addcalladapterfactory(rxjavacalladapterfactory.create()) .baseurl(basepar.getbaseurl()) .build(); /*rx处理*/ progresssubscriber subscriber = new progresssubscriber(basepar); observable observable = basepar.getobservable(retrofit) /*失败后的retry配置*/ .retrywhen(new retrywhennetworkexception()) /*生命周期管理*/ .compose(basepar.getrxappcompatactivity().bindtolifecycle()) /*http请求线程*/ .subscribeon(schedulers.io()) .unsubscribeon(schedulers.io()) /*回调线程*/ .observeon(androidschedulers.mainthread()) /*结果判断*/ .map(basepar); /*数据回调*/ observable.subscribe(subscriber); }
首先通过api接口类baseapi的实现类中数据初始化okhttpclient和retrofit对象,其中包含了url,超时等,接着通过baseapi的抽象方法getobservable得到observable对象,得到observable对象以后,我们就能随意的切换现成来处理,整个请求通过compose设定的rxlifecycle来管理生命周期,所以不会溢出和泄露无需任何担心,最后再服务器数据返回时,通过map判断结果,剔除错误信息,成功以后返回到自定义的progresssubscriber对象中,所以接下来封装progresssubscriber对象。
progresssubscriber封装
progresssubscriber其实是继承于subscriber,封装的方法无非是对subscriber的回调方法的封装
- onstart():开始
- oncompleted():结束
- onerror(throwable e):错误
- onnext(t t):成功
1.请求加载框
http请求都伴随着加载框的使用,所以这里需要在onstart()使用前初始一个加载框,这里简单的用progressdialog代替
/** * 用于在http请求开始时,自动显示一个progressdialog * 在http请求结束是,关闭progressdialog * 调用者自己对请求数据进行处理 * created by wzg on 2016/7/16. */ public class progresssubscriber<t> extends subscriber<t> { /*是否弹框*/ private boolean showporgress = true; /* 软引用回调接口*/ private softreference<httponnextlistener> msubscriberonnextlistener; /*软引用反正内存泄露*/ private softreference<rxappcompatactivity> mactivity; /*加载框可自己定义*/ private progressdialog pd; /*请求数据*/ private baseapi api; /** * 构造 * * @param api */ public progresssubscriber(baseapi api) { this.api = api; this.msubscriberonnextlistener = api.getlistener(); this.mactivity = new softreference<>(api.getrxappcompatactivity()); setshowporgress(api.isshowprogress()); if (api.isshowprogress()) { initprogressdialog(api.iscancel()); } } /** * 初始化加载框 */ private void initprogressdialog(boolean cancel) { context context = mactivity.get(); if (pd == null && context != null) { pd = new progressdialog(context); pd.setcancelable(cancel); if (cancel) { pd.setoncancellistener(new dialoginterface.oncancellistener() { @override public void oncancel(dialoginterface dialoginterface) { oncancelprogress(); } }); } } } /** * 显示加载框 */ private void showprogressdialog() { if (!isshowporgress()) return; context context = mactivity.get(); if (pd == null || context == null) return; if (!pd.isshowing()) { pd.show(); } } /** * 隐藏 */ private void dismissprogressdialog() { if (!isshowporgress()) return; if (pd != null && pd.isshowing()) { pd.dismiss(); } } }
由于progress的特殊性,需要指定content而且不能是application所以这里传递一个rxappcompatactivity,而同时上面的httpmanager同样需要,所以这里统一还是按照baseapi传递过来,使用软引用的方式避免泄露。剩下的无非是初始化,显示和关闭方法,可以详细看代码。
2.onstart()实现
在onstart()中需要调用加载框,然后这里还有网络缓存的逻辑,后面会单独讲解,现在先忽略它的存在。
/** * 订阅开始时调用 * 显示progressdialog */ @override public void onstart() { showprogressdialog(); /*缓存并且有网*/ if (api.iscache() && apputil.isnetworkavailable(rxretrofitapp.getapplication())) { /*获取缓存数据*/ cookieresulte cookieresulte = cookiedbutil.getinstance().querycookieby(api.geturl()); if (cookieresulte != null) { long time = (system.currenttimemillis() - cookieresulte.gettime()) / 1000; if (time < api.getcookienetworktime()) { if (msubscriberonnextlistener.get() != null) { msubscriberonnextlistener.get().oncachenext(cookieresulte.getresulte()); } oncompleted(); unsubscribe(); } } } }
3.oncompleted()实现
/** * 完成,隐藏progressdialog */ @override public void oncompleted() { dismissprogressdialog(); }
4.onerror(throwable e)实现
在onerror(throwable e)是对错误信息的处理和缓存读取的处理,后续会讲解,先忽略。
/** * 对错误进行统一处理 * 隐藏progressdialog * * @param e */ @override public void onerror(throwable e) { dismissprogressdialog(); /*需要緩存并且本地有缓存才返回*/ if (api.iscache()) { observable.just(api.geturl()).subscribe(new subscriber<string>() { @override public void oncompleted() { } @override public void onerror(throwable e) { errordo(e); } @override public void onnext(string s) { /*获取缓存数据*/ cookieresulte cookieresulte = cookiedbutil.getinstance().querycookieby(s); if (cookieresulte == null) { throw new httptimeexception("网络错误"); } long time = (system.currenttimemillis() - cookieresulte.gettime()) / 1000; if (time < api.getcookienonetworktime()) { if (msubscriberonnextlistener.get() != null) { msubscriberonnextlistener.get().oncachenext(cookieresulte.getresulte()); } } else { cookiedbutil.getinstance().deletecookie(cookieresulte); throw new httptimeexception("网络错误"); } } }); } else { errordo(e); } } /*错误统一处理*/ private void errordo(throwable e) { context context = mactivity.get(); if (context == null) return; if (e instanceof sockettimeoutexception) { toast.maketext(context, "网络中断,请检查您的网络状态", toast.length_short).show(); } else if (e instanceof connectexception) { toast.maketext(context, "网络中断,请检查您的网络状态", toast.length_short).show(); } else { toast.maketext(context, "错误" + e.getmessage(), toast.length_short).show(); } if (msubscriberonnextlistener.get() != null) { msubscriberonnextlistener.get().onerror(e); } }
5.onnext(t t)实现
/** * 将onnext方法中的返回结果交给activity或fragment自己处理 * * @param t 创建subscriber时的泛型类型 */ @override public void onnext(t t) { if (msubscriberonnextlistener.get() != null) { msubscriberonnextlistener.get().onnext(t); } }
主要是是将得到的结果,通过自定义的接口返回给view界面,其中的软引用对象msubscriberonnextlistener是自定义的接口回调类httponnextlistener.
6.httponnextlistener封装
现在只需关心onnext(t t)和onerror(throwable e)接口即可,回调的触发点都是在上面的progresssubscriber中调用
/** * 成功回调处理 * created by wzg on 2016/7/16. */ public abstract class httponnextlistener<t> { /** * 成功后回调方法 * @param t */ public abstract void onnext(t t); /** * 緩存回調結果 * @param string */ public void oncachenext(string string){ } /** * 失败或者错误方法 * 主动调用,更加灵活 * @param e */ public void onerror(throwable e){ } /** * 取消回調 */ public void oncancel(){ } }
失败后的retry处理
这里你可能会问,retrofit有自带的retry处理呀,的确retrofit有自带的retry处理,但是有很多的局限,先看下使用
okhttpclient.builder builder = new okhttpclient.builder(); builder.retryonconnectionfailure(true);
使用起来还是很方便,只需要调用一个方法即可,但是它是不可控的,也就是没有办法设置retry时间次数,所以不太灵活,既然如此还不如自己封装一下,因为用rxjava实现这个简直小菜,无形中好像已经给rxjava打了广告,中毒太深。
很简单直接上代码:
/** * retry条件 * created by wzg on 2016/10/17. */ public class retrywhennetworkexception implements func1<observable<? extends throwable>, observable<?>> { // retry次数 private int count = 3; // 延迟 private long delay = 3000; // 叠加延迟 private long increasedelay = 3000; public retrywhennetworkexception() { } public retrywhennetworkexception(int count, long delay) { this.count = count; this.delay = delay; } public retrywhennetworkexception(int count, long delay, long increasedelay) { this.count = count; this.delay = delay; this.increasedelay = increasedelay; } @override public observable<?> call(observable<? extends throwable> observable) { return observable .zipwith(observable.range(1, count + 1), new func2<throwable, integer, wrapper>() { @override public wrapper call(throwable throwable, integer integer) { return new wrapper(throwable, integer); } }).flatmap(new func1<wrapper, observable<?>>() { @override public observable<?> call(wrapper wrapper) { if ((wrapper.throwable instanceof connectexception || wrapper.throwable instanceof sockettimeoutexception || wrapper.throwable instanceof timeoutexception) && wrapper.index < count + 1) { //如果超出重试次数也抛出错误,否则默认是会进入oncompleted return observable.timer(delay + (wrapper.index - 1) * increasedelay, timeunit.milliseconds); } return observable.error(wrapper.throwable); } }); } private class wrapper { private int index; private throwable throwable; public wrapper(throwable throwable, int index) { this.index = index; this.throwable = throwable; } } }
使用
到这里,我们第一步封装已经完成了,下面讲解下如何使用,已经看明白的各位看官,估计早就看明白了使用方式,无非是创建一个api对象继承baseapi初始接口信息,然后调用httpmanager对象的dohttpdeal(baseapi basepar)方法,最后静静的等待回调类httponnextlistener<t>类返回的onnext(t t)成功数据或者onerror(throwable e)数据。
其实代码就是这样:
api接口对象
/** * 测试数据 * created by wzg on 2016/7/16. */ public class subjectpostapi extends baseapi { // 接口需要传入的参数 可自定义不同类型 private boolean all; /*任何你先要传递的参数*/ // string xxxxx; /** * 默认初始化需要给定回调和rx周期类 * 可以额外设置请求设置加载框显示,回调等(可扩展) * @param listener * @param rxappcompatactivity */ public subjectpostapi(httponnextlistener listener, rxappcompatactivity rxappcompatactivity) { super(listener,rxappcompatactivity); setshowprogress(true); setcancel(true); setcache(true); setmothed("appfiftytonegraph/videolink"); setcookienetworktime(60); setcookienonetworktime(24*60*60); } public boolean isall() { return all; } public void setall(boolean all) { this.all = all; } @override public observable getobservable(retrofit retrofit) { httppostservice service = retrofit.create(httppostservice.class); return service.getallvediobys(isall()); } }
请求回调
// 完美封装简化版 private void simpledo() { subjectpostapi postentity = new subjectpostapi(simpleonnextlistener,this); postentity.setall(true); httpmanager manager = httpmanager.getinstance(); manager.dohttpdeal(postentity); } // 回调一一对应 httponnextlistener simpleonnextlistener = new httponnextlistener<list<subjectresulte>>() { @override public void onnext(list<subjectresulte> subjects) { tvmsg.settext("网络返回:\n" + subjects.tostring()); } @override public void oncachenext(string cache) { /*缓存回调*/ gson gson=new gson(); java.lang.reflect.type type = new typetoken<baseresultentity<list<subjectresulte>>>() {}.gettype(); baseresultentity resultentity= gson.fromjson(cache, type); tvmsg.settext("缓存返回:\n"+resultentity.getdata().tostring() ); } /*用户主动调用,默认是不需要覆写该方法*/ @override public void onerror(throwable e) { super.onerror(e); tvmsg.settext("失败:\n" + e.tostring()); } /*用户主动调用,默认是不需要覆写该方法*/ @override public void oncancel() { super.oncancel(); tvmsg.settext("取消請求"); } };
后续
到这里,封装功能中很多功能还没涉及和讲解,后续会陆续更新!
先给大家看看为师的完全体功能:
1.retrofit+rxjava+okhttp基本使用方法
2.统一处理请求数据格式
3.统一的progressdialog和回调subscriber处理
4.取消http请求
5.预处理http请求
6.返回数据的统一判断
7.失败后的retry处理
8.rxlifecycle管理生命周期,防止泄露
9.文件上传下载(支持多文件,断点续传)
10.cache数据持久化和数据库(greendao)两种缓存机制
11.异常统一处理
来个图压压惊:
源码:
rxretrofit-终极封装-深入浅出&网络请求-github
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 百度统计正式上线白名单过滤功能