Android 使用Retrofit2.0+OkHttp3.0实现缓存处理+Cookie持久化第三方库
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
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 很强大,能帮我处理很多复杂的场景,如果熟练使用的话,那么能提升我们的开发效率。
如果无聊的话可以看看原理:
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(...)==>执行在主线程,创建观察者,作为事件传递的终点处理事件
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持久化第三方库的更多相关文章
- Android studio module生成jar包,module中引用的第三方库没有被引用,导致java.lang.NoClassDefFoundError错误。
android studio 创建了一个Module生成jar包,这个module中有引用一些第三方的类库,比如 gson,volley等. 但是生成的jar包里,并没有将gson,volley等第三 ...
- Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava
Retrofit2.0通俗易懂的学习姿势,Retrofit2.0 + OkHttp3 + Gson + RxJava Retrofit,因为其简单与出色的性能,也是受到很多人的青睐,但是他和以往的通信 ...
- Android基于Retrofit2.0 +RxJava 封装的超好用的RetrofitClient工具类(六)
csdn :码小白 原文地址: http://blog.csdn.net/sk719887916/article/details/51958010 RetrofitClient 基于Retrofit2 ...
- [Android] Android RxJava2+Retrofit2+OkHttp3 的使用(一) --基础篇 Retrofit2 的使用
本文是 Android RxJava2+Retrofit2+OkHttp3 的使用(一) --基础篇 Retrofit2 的使用 本文的目标是用 Retrofit写一个网络请求: 本文以从获取天气预报 ...
- Android 百度地图 SDK v3.0.0 (三) 添加覆盖物Marker与InfoWindow的使用
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37737213 上篇博客已经实现了地图的定位以及结合了方向传感器用户路痴定位方向, ...
- Android 百度地图 SDK v3.0.0 (三) 加入覆盖Marker与InfoWindow使用
转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/37737213 上篇博客已经实现了地图的定位以及结合了方向传感器用户路痴定位方向, ...
- 【我的Android进阶之旅】解决sqlcipher库:java.lang.IllegalStateException: get field slot from row 0 col 0 failed.
一.背景 最近维护公司的大数据SDK,在大数据SDK里面加入了ANR的监控功能,并将ANR的相关信息通过大数据埋点的方式记录到了数据库中,然后大数据上报的时候上报到大数据平台,这样就可以实现ANR性能 ...
- Android消息传递之EventBus 3.0使用详解
前言: 前面两篇不仅学习了子线程与UI主线程之间的通信方式,也学习了如何实现组件之间通信,基于前面的知识我们今天来分析一下EventBus是如何管理事件总线的,EventBus到底是不是最佳方案?学习 ...
- Xamarin For Visual Studio 3.0.54.0 完整离线破解版(C# 开发Android、IOS工具 吾乐吧软件站分享)
Xamarin For Visual Studio就是原本的Xamarin For Android 以及 Xamarin For iOS,最新版的已经把两个独立的插件合并为一个exe安装包了.为了区分 ...
随机推荐
- Use Exception.ToString() instead of Exception.Message.
Exception.Message contains only the message (doh) associated with the exception. Example: Object ref ...
- Linux centos6.x 配置免密码登录
免密码登录主要就是被访机器提供公匙给访问者,然后访问者使用ssh协议时可以使用所配置好的公匙验证.这样就免去了输入密码的麻烦. 某些集群例如hadoop,一般都需要将主机和其他机器间配置无密码公匙认证 ...
- 【起航计划 009】2015 起航计划 Android APIDemo的魔鬼步伐 08 App->Activity->QuickContactsDemo 联系人 ResourceCursorAdapter使用 QuickContactBadge使用
QuickContactsDemo示例介绍了如何使用Content Provider来访问Android系统的Contacts 数据库. Content Provider为不同应用之间共享数据提供了统 ...
- Struts2_用DomainModel接收参数
用域模型接收参数 User类 package com.bjsxt.struts2.user.model; public class User { private String name; privat ...
- [转]用jwplayer+Nginx搭建视频点播服务器,解决拖动加载慢的问题
flv视频可以采用两种方式发布: 一.普通的HTTP下载方式 二.基于Flash Media Server或Red5服务器的rtmp/rtmpt流媒体方式. 多数知名视频网站都采用的是前一种方式. 两 ...
- JavaScript 事件兼容性写法
1.以下是JavaScript事件兼容性写法,使用者可以随意使用,兼容所有浏览器.包括IE6(亲测) <!DOCTYPE html> <html> <head> & ...
- python 爬图片
学了两天python,语法慢慢熟悉吧,数据结构都没写过. 写了一个爬图片的小东西.挺有意思的.都是女神照 (✿◡‿◡) 用的是正则表达式, ''' 符号: . 匹配任意字符,\n除外 * 匹配前一个字 ...
- 带有data-ng-bind表达式
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- 前端JavaScript之DOM节点操作
1.HTML DOM是啥 Document Object Model:定义了访问和操作HTML文档的标准方法,把HTML文档呈现为带有元素,属性和文本的树状结构 2.解析过程 HTML加载完毕,渲染引 ...
- java mysql多次事务 模拟依据汇率转账,并存储转账信息 分层完成 dao层 service 层 client层 连接池使用C3p0 写入库使用DBUtils
Jar包使用,及层的划分 c3p0-config.xml <?xml version="1.0" encoding="UTF-8"?> <c3 ...