关于OkHttp缓存post请求的问题
Posted this.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于OkHttp缓存post请求的问题相关的知识,希望对你有一定的参考价值。
现有这样一个要求,使用Retorfit+okhttp需要在有网的时候能够连接服务器,读取相关信息;在没网络断开的时候需要读取Okhttp的缓存来达到离线的效果。
基于上述的需求,可以使用Okhttp的拦截器来实现:
//设置缓存目录
File cacheFile = new File(BaseApplication.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
//配置okhttp
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
.addNetworkInterceptor(mRewriteCacheControlInterceptor)
.addInterceptor(headerInterceptor)
.addInterceptor(logInterceptor)
.cache(cache) //设置缓存
.build();
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").serializeNulls().create();
retrofit = new Retrofit.Builder()
.client(okHttpClient)
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
//配置拦截器
private final Interceptor mRewriteCacheControlInterceptor = new Interceptor()
@Override
public Response intercept(Chain chain) throws IOException
Request request = chain.request();
String cacheControl = request.cacheControl().toString();
if (!NetWorkUtils.isNetConnected(BaseApplication.getContext()))
request = request.newBuilder()
.cacheControl(TextUtils.isEmpty(cacheControl)? CacheControl.FORCE_CACHE:CacheControl.FORCE_NETWORK)
.build();
Response originalResponse = chain.proceed(request);
if (NetWorkUtils.isNetConnected(BaseApplication.getContext()))
//有网的时候连接服务器请求,缓存一天
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + MAX_AGE)
.removeHeader("Pragma")
.build();
else
//网络断开时读取缓存
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + CACHE_STALE_SEC)
.removeHeader("Pragma")
.build();
;
通过以上就可以实现缓存。
但是,在使用该拦截器去执行POST请求的时候,会发现,即使在log中看到了读取缓存,但是实际上缓存目录里什么都没有。实际上是因为get请求一般较为持久,而post需要携带参数,会经常改动,所以没必要缓存,这个机制从Okhttp的源码里也可以看到:
//Cache类的put方法
private CacheRequest put(Response response) throws IOException
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method()))
try
remove(response.request());
catch (IOException ignored)
return null;
//如果请求方式不用get,就直接跳过了
if (!requestMethod.equals("GET"))
return null;
//省略代码
但是,有些情况下确实需要去缓存post请求,要怎么去实现呢?
- Sqlite
网络正常时缓存响应信息到数据库,在没有网络的时候读出数据。 - DiskLruCache
通过文件缓存到本地。
这里是通过Sqlite来实现,至于如果通过DiskLruCache可以参考手动缓存Retrofit+OkHttp响应体,不再局限于Get请求缓存 。
通过Sqlite,需要缓存的基本信息有URL,Params, Response(链接,参数,响应结果)。当然可能还会缓存一些期限时长之类的字段,可以用于清理过期的缓存等。这里以基本的信息来实现。
//创建数据库和表
public class MyDBHelper extends SQLiteOpenHelper
private static final String DB_NAME = "test.db";
private static final int DB_VERSION = 1;
static final String CACHE_TABLE = "cache";
public MyDBHelper(Context context)
super(context, DB_NAME, null, DB_VERSION);
@Override
public void onCreate(SQLiteDatabase db)
String sql = "create table if not exists " + CACHE_TABLE +
" (url text, params text, response text)";
db.execSQL(sql);
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
String sql = "DROP TABLE IF EXISTS " + CACHE_TABLE;
db.execSQL(sql);
onCreate(db);
另外,还需创建一个数据库管理类CacheDao,用于增删该改查:
public class CacheDao
private static volatile CacheDao cacheDao;
private MyDBHelper helper;
private SQLiteDatabase database;
private CacheDao(Context context)
helper = new MyDBHelper(context.getApplicationContext());
database = helper.getWritableDatabase();
public static CacheDao getInstance(Context context)
if (cacheDao == null)
synchronized (CacheDao.class)
if (cacheDao == null)
cacheDao = new CacheDao(context);
return cacheDao;
//查
public String queryResponse(String urlKey, String params)
return null;
//增
public void insertResponse(String urlKey, String params, String value)
//改
public void updateResponse(String urlKey, String params, String value)
//删
public void deleteResponse(String urlKey, String params)
数据库创建完成后,关键是要和Okhttp的请求拦截串联起来使用:
private final Interceptor mRewriteCacheControlInterceptor = new Interceptor()
@Override
public Response intercept(Chain chain) throws IOException
Request request = chain.request();
String url = request.url().toString(); //获取请求URL
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
String params = buffer.readString(Charset.forName("UTF-8")); //获取请求参数
Response response;
if (NetWorkUtils.isNetConnected(BaseApplication.getContext()))
int maxAge = 60 * 60*24;
//如果网络正常,执行请求。
Response originalResponse = chain.proceed(request);
//获取MediaType,用于重新构建ResponseBody
MediaType type = originalResponse.body().contentType();
//获取body字节即响应,用于存入数据库和重新构建ResponseBody
byte[] bs = originalResponse.body().bytes();
response = originalResponse.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
//重新构建body,原因在于body只能调用一次,之后就关闭了。
.body(ResponseBody.create(type, bs))
.build();
//将响应插入数据库
cacheDao.insertResponse(url, params, new String(bs, "GB2312"));
else
//没有网络的时候,由于Okhttp没有缓存post请求,所以不要调用chain.proceed(request),会导致连接不上服务器而抛出异常(504)
String b = cacheDao.queryResponse(url, params); //读出响应
Log.d("OkHttp", "request:" + url);
Log.d("OkHttp", "request method:" + request.method());
Log.d("OkHttp", "response body:" + b);
int maxStale = 60 * 60 * 24 * 28;
//构建一个新的response响应结果
response = new Response.Builder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.body(ResponseBody.create(MediaType.parse("application/json"), b.getBytes()))
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.build();
return response;
;
由于在没有网络的时候不调用chain.proceed(request),所以拦截器就在此中断,直接将response结果返回。
通过上述方式,即可实现post请求结果的缓存。
以上是关于关于OkHttp缓存post请求的问题的主要内容,如果未能解决你的问题,请参考以下文章