OKHttp3(支持Retrofit)的网络数据缓存Interceptor拦截器的实现

网友投稿 409 2023-03-06


OKHttp3(支持Retrofit)的网络数据缓存Interceptor拦截器的实现

前言:前段时间在开发APP的时候,经常出现由于用户设备环境的原因,拿不到从网络端获取的数据,所以在APP端展现的结果总是一个空白的框,这种情况对于用户体验来讲是极其糟糕的,所以,苦思冥想决定对OKHTTP下手(因为我在项目中使用的网络请求框架就是OKHTTP),则 写了这么一个网络数据缓存拦截器。

OK,那么我们决定开始写了,我先说一下思路:

思路篇

既然要写的是网络数据缓存拦截器,主要是利用了OKHTTP强大的拦截器功能,那么我们应该对哪些数据进行缓存呢,或者在哪些情况下启用数据进行缓存机制呢?

第一 :支持POST请求,因为官方已经提供了一个缓存拦截器,但是有一个缺点,就是只能对GET请求的数据进行缓存,对POST则不支持。

第二 :网络正常的时候,则是去网络端取数据,如果网络异常,比如TimeOutException UnKnowHostException 诸如此类的问题,那么我们就需要去缓存取出数据返回。

第三 :如果从缓存中取出的数据是空的,那么我们还是需要让这次请求走剩下的正常的流程。

第四 :调用者必须对缓存机制完全掌控,可以根据自己的业务需求选择性的对数据决定是否进行缓存。

第五 :使用必须简单,这是最最最最重要的一点。

好,我们上面罗列了五点是我们的大概思路,现在来说一下代码部分:

代码篇

缓存框架 :我这里使用的缓存框架是DiskLruCache https://github.com/JakeWharton/DiskLruCache 这个缓存框架可以存储到本地,也经过谷歌认可,这也是选择这个框架的主要原因。我这里也对缓存框架进行封装了一个CacheManager类:

import android.content.Context;

import android.content.pm.PackageInfo;

import android.content.pm.PackageManager;

import com.xiaolei.OkhttpCacheInterceptor.Log.Log;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

/**

* Created by xiaolei on 2017/5/17.

*/

public class CacheManager

{

public static final String TAG = "CacheManager";

//max cache size 10mb

private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10;

private static final int DISK_CACHE_INDEX = 0;

private static final String CACHE_DIR = "responses";

private DiskLruCache mDiskLruCache;

private volatile static CacheManager mCacheManager;

public static CacheManager getInstance(Context context)

{

if (mCacheManager == null)

{

synchronized (CacheManager.class)

{

if (mCacheManager == null)

{

mCacheManager = new CacheManager(context);

}

}

}

return mCacheManager;

}

private CacheManager(Context context)

{

File diskCacheDir = getDiskCacheDir(context, CACHE_DIR);

if (!diskCacheDir.exists())

{

boolean b = diskCacheDir.mkdirs();

Log.d(TAG, "!diskCacheDir.exists() --- diskCacheDir.mkdirs()=" + b);

}

if (diskCacheDir.getUsableSpace() > DISK_CACHE_SIZE)

{

try

{

mDiskLruCache = DiskLruCache.open(diskCacheDir,

getAppVersion(context), 1/*一个key对应多少个文件*/, DISK_CACHE_SIZE);

Log.d(TAG, "mDiskLruCache created");

} catch (IOException e)

{

e.printStackTrace();

}

}

}

/**

* 同步设置缓存

*/

public void putCache(String key, String value)

{

if (mDiskLruCache == null) return;

OutputStream os = null;

try

{

DiskLruCache.Editor editor = mDiskLruCache.edit(encryptMD5(key));

os = editor.newOutputStream(DISK_CACHE_INDEX);

os.write(value.getBytes());

os.flush();

editor.commit();

mDiskLruCache.flush();

} catch (IOException e)

{

e.printStackTrace();

} finally

{

if (os != null)

{

try

{

os.close();

} catch (IOException e)

{

e.printStackTrace();

}

}

}

}

/**

* 异步设置缓存

*/

public void setCache(final String key, final String value)

{

new Thread()

{

@Override

public void run()

{

putCache(key, value);

}

}.start();

}

/**

* 同步获取缓存

*/

public String getCache(String key)

{

if (mDiskLruCache == null)

{

return null;

}

FileInputStream fis = null;

ByteArrayOutputStream bos = null;

try

{

DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptMD5(key));

if (snapshot != null)

{

fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);

bos = new ByteArrayOutputStream();

byte[] buf = new byte[1024];

int len;

while ((len = fis.read(buf)) != -1)

{

bos.write(buf, 0, len);

}

byte[] data = bos.toByteArray();

return new String(data);

}

} catch (IOException e)

{

e.printStackTrace();

} finally

{

if (fis != null)

{

try

{

fis.close();

} catch (IOException e)

{

e.printStackTrace();

}

}

if (bos != null)

{

try

{

bos.close();

} catch (IOException e)

{

e.printStackTrace();

}

}

}

return null;

}

/**

* 异步获取缓存

*/

public void getCache(final String key, final CacheCallback callback)

{

new Thread()

{

@Override

public void run()

{

String cache = getCache(key);

callback.onGetCache(cache);

}

}.start();

}

/**

* 移除缓存

*/

public boolean removeCache(String key)

{

if (mDiskLruCache != null)

{

try

{

return mDiskLruCache.remove(encryptMD5(key));

} catch (IOException e)

{

e.printStackTrace();

}

}

return false;

}

/**

* 获取缓存目录

*/

private File getDiskCacheDir(Context context, String uniqueName)

{

String cachePath = context.getCacheDir().getPath();

return new File(cachePath + File.separator + uniqueName);

}

/**

* 对字符串进行MD5编码

*/

public static String encryptMD5(String string)

{

try

{

byte[] hash = MessageDigest.getInstance("MD5").digest(

string.getBytes("UTF-8"));

StringBuilder hex = new StringBuilder(hash.length * 2);

for (byte b : hash)

{

if ((b & 0xFF) < 0x10)

{

hex.append("0");

}

hex.append(Integer.toHexString(b & 0xFF));

}

return hex.toString();

} catch (NoSuchAlgorithmException | UnsupportedEncodingException e)

{

e.printStackTrace();

}

return string;

}

/**

* 获取APP版本号

*/

private int getAppVersion(Context context)

{

PackageManager pm = context.getPackageManager();

try

{

PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);

return pi == null ? 0 : pi.versionCode;

} catch (PackageManager.NameNotFoundException e)

{

e.printStackTrace();

}

return 0;

}

}

缓存CacheInterceptor拦截器:利用OkHttp的Interceptor拦截器机制,智能判断缓存场景,以及网络情况,对不同的场景进行处理。

import android.content.Context;

import com.xiaolei.OkhttpCacheInterceptor.Catch.CacheManager;

import com.xiaolei.OkhttpCacheInterceptor.Log.Log;

import java.io.IOException;

import okhttp3.FormBody;

import okhttp3.Interceptor;

import okhttp3.Protocol;

import okhttp3.Request;

import okhttp3.Response;

import okhttp3.ResponseBody;

/**

* 字符串的缓存类

* Created by xiaolei on 2017/12/9.

*/

public class CacheInterceptor implements Interceptor

{

private Context context;

public void setContext(Context context)

{

this.context = context;

}

public CacheInterceptor(Context context)

{

this.context = context;

}

@Override

public Response intercept(Chain chain) throws IOException

{

Request request = chain.request();

String cacheHead = request.header("cache");

String cache_control = request.header("Cache-Control");

if ("true".equals(cacheHead) || // 意思是要缓存

(cache_control != null && !cache_control.isEmpty())) // 这里还支持WEB端协议的缓存头

{

long oldnow = System.currentTimeMillis();

String url = request.url().url().toString();

String responStr = null;

String reqBodyStr = getPostParams(request);

try

{

Response response = chain.proceed(request);

if (response.isSuccessful()) // 只有在网络请求返回成功之后,才进行缓存处理,否则,404存进缓存,岂不笑话

{

ResponseBody responseBody = response.body();

if (responseBody != null)

{

responStr = responseBody.string();

if (responStr == null)

{

responStr = "";

}

CacheManager.getInstance(context).setCache(CacheManager.encryptMD5(url + reqBodyStr), responStr);//存缓存,以链接+参数进行MD5编码为XTAjOybopKEY存

Log.i("HttpRetrofit", "--> Push Cache:" + url + " :Success");

}

return getOnlineResponse(response, responStr);

} else

{

return chain.proceed(request);

}

} catch (Exception e)

{

Response response = getCacheResponse(request, oldnow); // 发生异常了,我这里就开始去缓存,但是有可能没有缓存,那么久需要丢给下一轮处理了

if (response == null)

{

return chain.proceed(request);//丢给下一轮处理

} else

{

return response;

}

}

} else

{

return chain.proceed(request);

}

}

private Response getCacheResponse(Request request, long oldNow)

{

Log.i("HttpRetrofit", "--> Try to Get Cache --------");

String url = request.url().url().toString();

String params = getPostParams(request);

String cacheStr = CacheManager.getInstance(context).getCache(CacheManager.encryptMD5(url + params));//取缓存,以链接+参数进行MD5编码为KEY取

if (cacheStr == null)

{

Log.i("HttpRetrofit", "<-- Get Cache Failure ---------");

return null;

}

Response response = new Response.Builder()

.code(200)

.body(ResponseBody.create(null, cacheStr))

.request(request)

.message("OK")

.protocol(Protocol.HTTP_1_0)

.build();

long useTime = System.currentTimeMillis() - oldNow;

Log.i("HttpRetrofit", "<-- Get Cache: " + response.code() + " " + response.message() + " " + url + " (" + useTime + "ms)");

Log.i("HttpRetrofit", cacheStr + "");

return response;

}

private Response getOnlineResponse(Response response, String body)

{

ResponseBody responseBody = response.body();

return new Response.Builder()

.code(response.code())

.body(ResponseBody.create(responseBody == null ? null : responseBody.contentType(), body))

.request(response.request())

.message(response.message())

.protocol(response.protocol())

.build();

}

/**

* 获取在Post方式下。向服务器发送的参数

*

* @param request

* @return

*/

private String getPostParams(Request request)

{

String reqBodyStr = "";

String method = request.method();

if ("POST".equals(method)) // 如果是Post,则尽可能解析每个参数

{

StringBuilder sb = new StringBuilder();

if (request.body() instanceof FormBody)

{

FormBody body = (FormBody) request.body();

if (body != null)

{

for (int i = 0; i < body.size(); i++)

{

sb.append(body.encodedName(i)).append("=").append(body.encodedValue(i)).append(",");

}

sb.delete(sb.length() - 1, sb.length());

}

reqBodyStr = sb.toString();

sb.delete(0, sb.length());

}

}

return reqBodyStr;

}

}

以上是主体思路,以及主要实现代码,现在来说一下使用方式

使用方式:

gradle使用:

compile 'com.xiaolei:OkhttpCacheInterceptor:1.0.0'

由于是刚刚提交到Jcenter,可能会出现拉不下来的情况(暂时还未过审核),着急的读者可以再在你的Project:build.gradle里的repositories里新增我maven的链接:

allprojects {

repositories {

maven{url 'https://dl.bintray.com/kavipyouxiang/maven'}

}

}

我们新建一个项目,项目截图是这样的:

项目截图

demo很简单,一个主页面,一个Bean,一个Retrofit,一个网络请求接口

注意,因为是网络,缓存,有关,所以,毫无疑问我们要在manifest里面添加网络请求权限,文件读写权限:

使用的时候,你只需要为你的OKHttpClient添加一个Interceptor:

client = new OkHttpClient.Builder()

.addInterceptor(new CacheInterceptor(context))//添加缓存拦截器,添加缓存的支持

.retryOnConnectionFailure(true)//失败重连

.connectTimeout(30, TimeUnit.SECONDS)//网络请求超时时间单位为秒

.build();

如果你想哪个接口的数据缓存,那么久为你的网络接口,添加一个请求头CacheHeaders.java这个类里包含了所有的情况,一般情况下只需要CacheHeaders.NORMAL就可以了

public interface Net

{

@Headers(CacheHeaders.NORMAL) // 这里是关键

@FormUrlEncoded

@POST("geocoding")

public Call getIndex(@Field("a") String a);

}

业务代码:

Net net = retrofitBase.getRetrofit().create(Net.class);

Call call = net.getIndex("苏州市");

call.enqueue(new Callback()

{

@Override

public void onResponse(Call call, Response response)

{

DataBean data = response.body();

Date date = new Date();

textview.setText(date.getMinutes() + " " + date.getSeconds() + ":\n" + data + "");

}

@Override

public void onFailure(Call call, Throwable t)

{

textview.setText("请求失败!");

}

});

我们这里对网络请求,成功了,则在界面上输出文字,加上当前时间,网络失败,则输出一个请求失败。

大概代码就是这样子的,详细代码,文章末尾将贴出demo地址

看效果:演示图

这里演示了,从网络正常,到网络不正常,再恢复到正常的情况。

结尾

以上篇章就是整个从思路,到代码,再到效果图的流程,这里贴一下DEMO的地址,喜欢的可以点个Start

Demo地址:https://github.com/xiaolei123/OkhttpCacheInterceptor


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:浅谈java定时器的发展历程
下一篇:云服务器管理系统Api(云服务器方案)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~