1.Retrofit+OkHttp的缓存机制

1.1.第一点

在响应请求之后在 data/data/<包名>/cache 下建立一个response 文件夹,保存缓存数据。

1.2.第二点

这样我们就可以在请求的时候,如果判断到没有网络,自动读取缓存的数据。

1.3.第三点

同样这也可以实现,在我们没有网络的情况下,重新打开App可以浏览的之前显示过的内容。

1.4.第四点

也就是:判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取。

1.5.github地址+参考文章

  github地址:http://square.github.io/retrofit/

  参考文章:让我的项目也是用RxJava+OkHttp+Retrofit

  参考文章:Retrofit+RxJava+OkHttp让网络请求变得简单

  最重要的参考文章:使用Retrofit2+OkHttp3实现缓存处理

2.缓存实现方式

2.1.在build.gradle中引入Retrofit 

compile 'com.squareup.retrofit2:retrofit:2.1.0'//retrofit
compile 'com.google.code.gson:gson:2.6.2'//Gson 库
//下面两个是RxJava 和RxAndroid
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'//转换器,请求结果转换成Model
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'//配合Rxjava 使用

2.2.先开启OkHttp缓存

  在Retrofit2.0版本之后,Retrofit底层自动依赖了OkHttp,所以不用重复依赖Okhttp了。

File httpCacheDirectory = new File(MyApp.mContext.getCacheDir(), "responses");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.cache(cache).build();

  这一步设置缓存路径以及缓存大小,其中addInterceptor是添加拦截器,下一步详细讲。

2.3.设置OkHttp拦截器

  主要是拦截操作,包括控制缓存的最大生命值,控制缓存的过期时间。

  两个操作都是在Interceptor中进行的。

  通过CacheControl控制缓存数据。 

CacheControl.Builder cacheBuilder = new CacheControl.Builder();
cacheBuilder.maxAge(0, TimeUnit.SECONDS);//这个是控制缓存的最大生命时间
cacheBuilder.maxStale(365,TimeUnit.DAYS);//这个是控制缓存的过时时间
CacheControl cacheControl = cacheBuilder.build();

  

  设置拦截器。 

Request request = chain.request();
if(!StateUtils.isNetworkAvailable(MyApp.mContext)){
request = request.newBuilder()
.cacheControl(cacheControl)
.build();
}
Response originalResponse = chain.proceed(request);
if (StateUtils.isNetworkAvailable(MyApp.mContext)) {
int maxAge = 60; // read from cache
return originalResponse.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public ,max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
return originalResponse.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
如果.maxAge(0,TimeUnit.SECONDS)设置的时间比拦截器长是不起效果,
如果设置比拦截器设置的时间短就会以这个时间为主,我觉得是为了方便控制。
.maxStale(365, TimeUnit.DAYS)设置的是过时时间,
我觉得okthhp缓存分成了两个来考虑,
一个是为了请求时直接拿缓存省流量,
一个是为了下次进入应用时可以直接拿缓存。

2.4.真实案例例子。

public class RetrofitFactory {

    private static final Object Object = new Object();
/**
* 缓存机制
* 在响应请求之后在 data/data/<包名>/cache 下建立一个response 文件夹,保持缓存数据。
* 这样我们就可以在请求的时候,如果判断到没有网络,自动读取缓存的数据。
* 同样这也可以实现,在我们没有网络的情况下,重新打开App可以浏览的之前显示过的内容。
* 也就是:判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取。
* https://werb.github.io/2016/07/29/%E4%BD%BF%E7%94%A8Retrofit2+OkHttp3%E5%AE%9E%E7%8E%B0%E7%BC%93%E5%AD%98%E5%A4%84%E7%90%86/
*/
//这里是设置拦截器,供下面的函数调用,辅助作用。
private static final Interceptor cacheControlInterceptor = new Interceptor() {
      
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkUtil.isNetworkConnected(InitApp.AppContext)) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
} Response originalResponse = chain.proceed(request);
if (NetWorkUtil.isNetworkConnected(InitApp.AppContext)) {
// 有网络时 设置缓存为默认值
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma") // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build();
} else {
// 无网络时 设置超时为1周
int maxStale = 60 * 60 * 24 * 7;
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
}
};
private volatile static Retrofit retrofit; //这个人函数供外部调用,当请求数据时来调用
@NonNull
public static Retrofit getRetrofit() {
synchronized (Object) {
if (retrofit == null) {
// 指定缓存路径,缓存大小 50Mb
Cache cache = new Cache(new File(InitApp.AppContext.getCacheDir(), "HttpCache"),
1024 * 1024 * 50); // Cookie 持久化
ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(InitApp.AppContext)); OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache(cache)
.addInterceptor(cacheControlInterceptor)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true); // Log 拦截器
if (BuildConfig.DEBUG) {
builder = SDKManager.initInterceptor(builder);
} retrofit = new Retrofit.Builder()
.baseUrl(INewsApi.HOST)
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
}
}

  通过这样,我们就可以直接使用同一个Retrofit请求方法。

  无论是最新数据还是换成数据,都可以转化为我们需要的对象,直接使用。

  这里的SDK也是一个方便自己调试的拦截器,实现方法如下: 

public class SDKManager {
public static void initStetho(Context context){
Stetho.initializeWithDefaults(context);
} public static OkHttpClient.Builder initInterceptor(OkHttpClient.Builder builder){
HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
return builder;
}
}

  这里的HttpLoggingInterceptor是okhttp3中自带的一个谷歌浏览器调试方法类。

  比较简单实用。

  所以这里就是将缓存控制的拦截器以及日志拦截器加到OkHttpClient.Builder中了。

  将Cookie持久化也加到OkHttpClient.Builder中了。

  就是要用的的方法加到这个OkHttpClient.Builder中就行了。

3.Cookie持久化的第三方库使用方法

3.1.Cookie持久化的第三方库==>PersisitentCookieJar

  github地址:https://github.com/franmontiel/PersistentCookieJar

  参考文章:Android关于Https中Cookie的使用(PersistentCookieJar)

  关于Cookie可以参考这篇文章:深入解析Cookie技术。

  关于鸿洋大神封装的okhttputils也提供的cookie的持久化管理工具。

3.2.引入第三方包

  在根目录的build.gradle加入如下支持: 

allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}

  然后在项目依赖的build.gradle中添加如下代码:

dependencies {
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
}

3.3.使用方法==>so easy.

  首先需要在初始化时加入以下代码:

ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));

  然后在初始化OkHttpClient调用cookieJar,如下代码:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.build();

  然后,服务器就可以发送Cookie给我们,我们进行永久保存(或者临时保存)

  下一次请求时,服务器即可拿到Cookie进行数据查询操作了。

4.用Retrofit写一个网络请求

上面讲OkHttpClient.Builder设置完毕后,用到了Retrofit来请求。

  

4.1.创建一个Retrofit实例。  

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(INewsApi.HOST)
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

  这里配置了接口baseUrl+addConverterFactory

  baseUrl是我们请求的基地址。

  addConverterFactory是默认提供的Gson转换器,写这个即可。

  addCallAdapterFactory是默认提供的适配器工厂回调类,写这个即可。

4.2.创建一个实际接口。 

public interface IJokeApi {

    /**
* 获取段子正文内容
* http://www.toutiao.com/api/article/feed/?category=essay_joke&as=A115C8457F69B85&cp=585F294B8845EE1
*/
@GET("api/article/feed/?category=essay_joke")
Observable<JokeContentBean> getJokeContent(
@Query("max_behot_time") String maxBehotTime,
@Query("as") String as,
@Query("cp") String cp); /**
* 获取段子评论
* http://m.neihanshequ.com/api/get_essay_comments/?group_id=编号&count=数量&offset=偏移量
*/
@GET("http://m.neihanshequ.com/api/get_essay_comments/?count=20")
@Headers({"User-Agent:" + Constant.USER_AGENT_MOBILE})
Observable<JokeCommentBean> getJokeComment(
@Query("group_id") String groupId,
@Query("offset") int offset);
}
说明:定义了一个方法getJokeContent,使用get请求方式,加上@GET 标签,
标签后面是这个接口的 尾址,完整的地址应该是 baseUrl+尾址 ,
参数 使用@Query标签,
如果参数多的话可以用@QueryMap标签,接收一个Map

4.3.用Retrofit创建接口实例的方法,如何调用接口中的方法进行网络请求。

  数据怎么回调呢?

  这里用了一个订阅关系Observable。

  加入RxJava后的网络请求,返回不再是一个Call,而是一个Observable。

  在Activity或者Fragment传入一个Subscriber建立订阅关系,就可以在onNext中处理结果了。

Subscription subscription = movieService.getTop250(0,20)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieSubject>() {
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
@Override
public void onNext(MovieSubject movieSubject) {
mMovieAdapter.setMovies(movieSubject.subjects);
mMovieAdapter.notifyDataSetChanged();
}
});
RxJava 的好处是帮我处理线程之间的切换,
我们可以在指定订阅的在哪个线程,观察在哪个线程。
我们可以通过操作符进行数据变换。整个过程都是链式的,简化逻辑。
其中FlatMap 操作符 还可以解除多层嵌套的问题。
总之,RxJava 很强大,能帮我处理很多复杂的场景,如果熟练使用的话,那么能提升我们的开发效率。

  如果无聊的话可以看看原理:

  参考文章:给Android开发者的RxJava详解。

  参考文章:关于RxJava最友好地文章。

4.4.用Retrofit创建接口的实际方法。

 @Override
public void doLoadData(){
Map<String, String> map = ToutiaoUtil.getAsCp(); RetrofitFactory.getRetrofit().create(IJokeApi.class).getJokeContent(time, map.get(Constant.AS), map.get(Constant.CP))
.subscribeOn(Schedulers.io())
.map(new Function<JokeContentBean, List<JokeContentBean.DataBean.GroupBean>>() {
@Override
public List<JokeContentBean.DataBean.GroupBean> apply(@NonNull JokeContentBean jokeContentBean) throws Exception {
List<JokeContentBean.DataBean> data = jokeContentBean.getData();
for (JokeContentBean.DataBean dataBean : data) {
groupList.add(dataBean.getGroup());
}
time = jokeContentBean.getNext().getMax_behot_tim() + "";
return groupList;
}
})
.compose(view.<List<JokeContentBean.DataBean.GroupBean>>bindToLife())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<JokeContentBean.DataBean.GroupBean>>() {
@Override
public void accept(@NonNull List<JokeContentBean.DataBean.GroupBean> groupBeen) throws Exception {
if (groupBeen.size() > 0) {
doSetAdapter();
} else {
doShowNoMore();
}
}
}, ErrorAction.error()); }

  这是实际请求方法。

  说白了,Retrofit.create是返回一个Observable<T>对象。

  因为在接口API中已经定义。

  所以这里用订阅关系处理回调。

  以前常用的方法是Call的回调,一个onSuccess表示网络成功请求,一个onFailure表示网络请求失败。

  太low了。

  现在用Observable<T>来进行类似的操作。

  • 这里Observable.subscribeOn==>指定了被观察者执行的线程环境
  • map==>使用map操作来完成类型转换,前者转换成后者。
  • compose()==>方便多个流重复利用一系列操作符(这个我也不是特别理解)  
  • observeOn(Android...MainThread)==>将后面执行的线程环境切换为主线程,但这一句还在io线程
  • subscribe(...)==>执行在主线程,创建观察者,作为事件传递的终点处理事件

  关于Rxjava操作符compose()的理解,可以参考这篇文章。 

5.关于RxJava的不理解的地方

5.1.在用Retrofit返回的Observable<T>中调用了一个compose方法。 

 .compose(view.<List<JokeContentBean.DataBean.GroupBean>>bindToLife())

5.2.然后这个方法定义在IBaseListView中。

 /**
* 绑定生命周期
*/
<T> LifecycleTransformer<T> bindToLife();

5.3.执行bindToLife()地方在BaseFragment中。 

 /**
* 绑定生命周期
*/
@Override
public <T> LifecycleTransformer<T> bindToLife() {
return bindUntilEvent(FragmentEvent.DESTROY);
}

  bindUntilEvent方法定义在RxFragment中。

5.4.在BaseListFragment中实现了LazyLoadFragment懒加载中的fetchData抽象函数。

    @Override
public void fetchData() {
observable = RxBus.getInstance().register(BaseListFragment.TAG);
observable.subscribe(new Consumer<Integer>() {
@Override
public void accept(@NonNull Integer integer) throws Exception {
adapter.notifyDataSetChanged();
}
});
}

  这里的方法和JokeContentPresenter处理器中请求数据的方法中的一段代码及其相似。

5.5.在BaseListFragment中重写了onDestroy()

    @Override
public void onDestroy() {
RxBus.getInstance().unregister(BaseListFragment.TAG, observable);
super.onDestroy();
}

Android 使用Retrofit2.0+OkHttp3.0实现缓存处理+Cookie持久化第三方库的更多相关文章

  1. Android studio module生成jar包,module中引用的第三方库没有被引用,导致java.lang.NoClassDefFoundError错误。

    android studio 创建了一个Module生成jar包,这个module中有引用一些第三方的类库,比如 gson,volley等. 但是生成的jar包里,并没有将gson,volley等第三 ...

  2. Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava

    Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava Retrofit,因为其简单与出色的性能,也是受到很多人的青睐,但是他和以往的通信 ...

  3. Android基于Retrofit2.0 +RxJava 封装的超好用的RetrofitClient工具类(六)

    csdn :码小白 原文地址: http://blog.csdn.net/sk719887916/article/details/51958010 RetrofitClient 基于Retrofit2 ...

  4. [Android] Android RxJava2+Retrofit2+OkHttp3 的使用(一) --基础篇 Retrofit2 的使用

    本文是 Android RxJava2+Retrofit2+OkHttp3 的使用(一) --基础篇 Retrofit2 的使用 本文的目标是用 Retrofit写一个网络请求: 本文以从获取天气预报 ...

  5. Android 百度地图 SDK v3.0.0 (三) 添加覆盖物Marker与InfoWindow的使用

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37737213 上篇博客已经实现了地图的定位以及结合了方向传感器用户路痴定位方向, ...

  6. Android 百度地图 SDK v3.0.0 (三) 加入覆盖Marker与InfoWindow使用

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/37737213 上篇博客已经实现了地图的定位以及结合了方向传感器用户路痴定位方向, ...

  7. 【我的Android进阶之旅】解决sqlcipher库:java.lang.IllegalStateException: get field slot from row 0 col 0 failed.

    一.背景 最近维护公司的大数据SDK,在大数据SDK里面加入了ANR的监控功能,并将ANR的相关信息通过大数据埋点的方式记录到了数据库中,然后大数据上报的时候上报到大数据平台,这样就可以实现ANR性能 ...

  8. Android消息传递之EventBus 3.0使用详解

    前言: 前面两篇不仅学习了子线程与UI主线程之间的通信方式,也学习了如何实现组件之间通信,基于前面的知识我们今天来分析一下EventBus是如何管理事件总线的,EventBus到底是不是最佳方案?学习 ...

  9. Xamarin For Visual Studio 3.0.54.0 完整离线破解版(C# 开发Android、IOS工具 吾乐吧软件站分享)

    Xamarin For Visual Studio就是原本的Xamarin For Android 以及 Xamarin For iOS,最新版的已经把两个独立的插件合并为一个exe安装包了.为了区分 ...

随机推荐

  1. 【Microsoft Azure学习之旅】测试消息队列(Service Bus Queue)是否会丢消息

    组里最近遇到一个问题,微软的Azure Service Bus Queue是否可靠?是否会出现丢失消息的情况? 具体缘由如下, 由于开发的产品是SaaS产品,为防止消息丢失,跨Module消息传递使用 ...

  2. DIV命名规范

    DIV命名规范 企业DIV使用频率高的命名方法 网页内容类 --- 注释的写法: /* Footer */ 内容区/* End Footer */ 摘要: summary 箭头: arrow 商标:  ...

  3. Python基本数据类型(一)

    一.int的函数说明(部分函数Python2特有,Python3已删除,部分函数Python3新增:) class int(object): """ int(x=0) - ...

  4. 如何获得C4C里某个code字段对应的描述信息

    通过我这篇文章介绍的方法使用C4C OData服务去取服务订单数据(Sales Order): 如何用代码的方式取出SAP C4C销售订单创建后所有业务伙伴的数据 https://www.jiansh ...

  5. IOS 运行循环

    . 运行循环========================================在iOS的应用程序中,应用程序启动之后,系统即会创建一个运行循环监听用户的交互. 以下代码其本质是在运行循环 ...

  6. (转)Fidder详解之get和post请求

    https://www.cnblogs.com/langhuagungun/p/7737204.html 前言 本文会对Fidder这款工具的一些重要功 能,进行详细讲解,带大家进入Fidder的世界 ...

  7. javascript中parseInt(),08,09,返回0

    javascript中在使用parseInt(08).parseInt(09),进行整数转换的时候,返回值是0 工具/原料   浏览器 文本编辑器 方法/步骤     javascript中在使用pa ...

  8. PHP精度问题

    PHP 为任意精度数学计算提供了二进制计算器(Binary Calculator),它支持任意大小和精度的数字,以字符串形式描述 bcadd — 加法bccomp — 比较bcdiv — 相除bcmo ...

  9. arXiv 上传文章过程

      arXiv属于预印本服务的一种,是指科研工作者的研究成果还未在正式出版物上发表,而出于和同行交流目的自愿先在学术会议上或通过互联网发布的科研论文.科技报告等文章.与刊物发表的文章以及网页发布的文章 ...

  10. Linux(二) - Unix&Linux 基本概念

    主机 = 内核 + 实用工具 内核(kernel) 当计算机启动时,计算机要经历一系列动作,这些动作构成了引导过程.引导过程的最后一个动作是启动一个非常复杂的程序,该程序就被称为内核(Kernel) ...