数据持久化在如今移动app开发中已经愈来愈被你们承认,提升了用户体验和软件的稳定性,可是因为retrofit持久化的局限性,因此须要本身动手改造一个适合本身的数据持久化方案!html
下面咱们分两节讲解,一节讲述自带的retrofit-cache用法和缺陷,一节讲述本身定义的缓存处理方案java
因为retrofit是基于okhttp的,因此他的cache原理就是运用了okhttp的cookie处理;git
注意:这里自带的cookie前提是服务器提供了支持(返回头有cache信息),只有get请求才具有http的缓存功能,post没有!没有!没有github
1.http缓存相关头:Expires (实体标头,HTTP1.0+):一个GMT时间,试图告知客户端,在此日期内,能够信任并使用对应缓存中的副本,缺点是,一但客户端日期不许确.则可能致使失效数据库
2.Pragma : no-cache(常规标头,http1.0+) api
3.Cache-Control : (常规标头,HTTP1.1)浏览器
3.1 public:(仅为响应标头)响应:告知任何途径的缓存者,能够无条件的缓存该响应缓存
3.2 private(仅为响应标头):响应:告知缓存者(据我所知,是指用户代理,常见浏览器的本地缓存.用户也是指,系统用户.但也许,不该排除,某些网关,能够识别每一个终端用户的状况),只针对单个用户缓存响应. 且能够具体指定某个字段.如private –“username”,则响应头中,名为username的标头内容,不会被共享缓存.服务器
3.3 no-cache:告知缓存者,必须原本来本的转发原始请求,并告知任何缓存者,别直接拿你缓存的副本,糊弄人.你须要去转发个人请求,并验证你的缓存(若是有的话).对应名词:端对端重载.cookie
注解使用,具体方法具体设置(max-age设置的是保鲜时间)
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();复制代码
固然咱们确定想要动态设置,并且每个get方法都须要缓存保鲜处理,怎么解决呢?
OkHttpClient.Builder builder = new OkHttpClient.Builder();
/*缓存位置和大小*/
builder.cache(new Cache(MyApplication.app.getCacheDir(),10*1024*1024));复制代码
网上不少资源都是错误的,走了不少弯路,注意这里必定要返回一个新的Response 不让不会有结果显示
/** * get缓存方式拦截器 * Created by WZG on 2016/10/26. */
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkAvailable(MyApplication.app)) {//没网强制从缓存读取(必须得写,否则断网状态下,退出应用,或者等待一分钟后,就获取不到缓存)
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
Response responseLatest;
if (isNetworkAvailable(MyApplication.app)) {
int maxAge = 60; //有网失效一分钟
responseLatest = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 6; // 没网失效6小时
responseLatest= response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return responseLatest;
}
}复制代码
有网状况下,一分钟内访问的请求不会去真正http请求,而是从cache中获取;
没网状况下,一概从缓存获取,6小时过时时间。
addNetworkInterceptor在请求发生前和发生后都处理一遍,addInterceptor在有结果返回后处理一遍
注意:这里必定要两个方法同时设置才能保证生效,暂时没搞懂为何
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addNetworkInterceptor(new CacheInterceptor());
builder.addInterceptor(new CacheInterceptor());复制代码
如今你的retrofit就能自动给get添加cookie了!
自带数据持久化处理方便快捷简单,可是局限性太大,必须是get请求并且还须要服务器配合头文件返回处理,因此在实际开发中并不适用;因此才有了自定义cookie处理的方案
主要是经过greenDao数据库存放数据,在网络请求成功后保存数据,再次请求判断url是否已经存在缓存数据
有网络:onstart中判断再判断保鲜时间,若是有效返回缓存数据,无效则再一次请求数据!
无网络(包含各类失败):onError中判断处理,有效时间内返回数据,无效自定义的网络错误抛出异常!
记录返回数据,标识url,和缓存时间
/** * post請求緩存数据 * Created by WZG on 2016/10/26. */
@Entity
public class CookieResulte {
@Id
private long id;
/*url*/
private String url;
/*返回结果*/
private String resulte;
/*时间*/
private long time;
}复制代码
保持和封装1-4封装的一致性,将缓存的相关设置放入在BaseApi中,而且将baseUrl和超时connectionTime也包含进来,更加灵活
/** * 请求数据统一封装类 * Created by WZG on 2016/7/16. */
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;
}复制代码
注意:若是须要使用缓存功能必需要设置mothed参数(和baseurl拼成一个url标识缓存数据)
因为使用GsonConverterFactory自动解析数据,因此须要在自动转换前获得服务器返回的数据,咱们能够自定义Interceptor在addInterceptor(成功后调用)拦截数据,保存到本地数据库中!
/** * gson持久化截取保存数据 * Created by WZG on 2016/10/20. */
public class CookieInterceptor implements Interceptor {
private CookieDbUtil dbUtil;
/*是否缓存标识*/
private boolean cache;
public CookieInterceptor( boolean cache) {
dbUtil=CookieDbUtil.getInstance();
this.cache=cache;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if(cache){
ResponseBody body = response.body();
BufferedSource source = body.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = Charset.defaultCharset();
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String bodyString = buffer.clone().readString(charset);
String url = request.url().toString();
CookieResulte resulte= dbUtil.queryCookieBy(url);
long time=System.currentTimeMillis();
/*保存和更新本地数据*/
if(resulte==null){
resulte =new CookieResulte(url,bodyString,time);
dbUtil.saveCookie(resulte);
}else{
resulte.setResulte(bodyString);
resulte.setTime(time);
dbUtil.updateCookie(resulte);
}
}
return response;
}
}复制代码
由于缓存回调过程当中没法手动传递Gson对象,也就是ResulteEntity中的T泛型,因此自由单独添加一个方法,返回缓存数据!考虑到可能不须要回到因此写成了具体的方法,可主动覆盖!
/** * 成功回调处理 * 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){
}
*********
}复制代码
这里分两种状况,有网络-和无网络(包含各类失败不仅仅只是无网络)
有网
判断是否存在缓存,若是有判断保鲜时间,有效期内返回数据,失效在一块儿请求;
/** * 订阅开始时调用 * 显示ProgressDialog */
@Override
public void onStart() {
showProgressDialog();
/*缓存而且有网*/
if(api.isCache()&& AppUtil.isNetworkAvailable(MyApplication.app)){
/*获取缓存数据*/
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();
}
}
}
}复制代码
无网络(失败状况)
原理和有网络同样,可是额外的加入了rx异常处理,防止用户在处理工程中致使错误崩溃!而且无缓冲抛出自定义异常
/** * 对错误进行统一处理 * 隐藏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);
}
}复制代码
因为是返回的string数据,因此须要在回调onCacheNext中手动解析Gson数据
// 回调一一对应
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() );
}
};复制代码
好了,一套自定义的缓存方案就解决了!
优势:
1.有效的解决了post请求缓存的问题
2.能够同时缓存get数据
3.自定义更加灵活,可更换任意第三方库
缺点:
1.缓存数据没法和onext公用一个回到接口,致使须要手动解析数据(因为Gson自动转换致使)
因为Gson在回调的过程当中和使用过程当中给程序致使的一些列的限制,因此决定封装一个变种框架,去掉Gson自动解析回调功能,改用String回调,让回调接口一对多处理,而且解决缓存没法和成功统一回调的问题!欢迎你们关注!
若有帮助换start和follow!