关于OkHttp缓存post请求的问题

来源:互联网 发布:众力网络安装电话 编辑:程序博客网 时间:2024/06/05 11:56

现有这样一个要求,使用Retorfit+okhttp需要在有网的时候能够连接服务器,读取相关信息;在没网络断开的时候需要读取Okhttp的缓存来达到离线的效果。
基于上述的需求,可以使用Okhttp的拦截器来实现:

//设置缓存目录File cacheFile = new File(BaseApplication.getContext().getCacheDir(), "cache");Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb//配置okhttpOkHttpClient 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请求结果的缓存。

1 0
原创粉丝点击