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编码为KEY存
            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里面添加网络请求权限,文件读写权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

使用的时候,你只需要为你的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<DataBean> getIndex(@Field("a") String a);
}

业务代码:

Net net = retrofitBase.getRetrofit().create(Net.class);
    Call<DataBean> call = net.getIndex("苏州市");
    call.enqueue(new Callback<DataBean>()
    {
      @Override
      public void onResponse(Call<DataBean> call, Response<DataBean> response)
      {
        DataBean data = response.body();
        Date date = new Date();
        textview.setText(date.getMinutes() + " " + date.getSeconds() + ":\n" + data + "");
      }

      @Override
      public void onFailure(Call<DataBean> call, Throwable t)
      {
        textview.setText("请求失败!");
      }
    });

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

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

看效果:演示图

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

结尾

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android 中okhttp自定义Interceptor(缓存拦截器)

    Android 中okhttp自定义Interceptor(缓存拦截器) 前言: 新公司项目是没有缓存的,我的天,坑用户流量不是么.不知道有人就喜欢一个界面没事点来点去的么.怎么办?一个字"加". 由于项目的网络请求被我换成了retrofit.而retrofit的网络请求默认基于okhttp okhttp的缓存由返回的header 来决定.如果服务器支持缓存的话返回的headers里面会有这一句 "Cache-Control","max-age=time&

  • Android的OkHttp包中的HTTP拦截器Interceptor用法示例

    OkHttp(GitHub:https://github.com/square/okhttp) 的 Interceptor 就如同名称「拦截器」一样,拦截你的 Request 做一些你想做的事情再送出去.例如: 1.自动加上使用者目前使用的语言送出去取得对应语言的回传内容. 2.将 Request 计算出这个 Request 的 sigunature 再附加上送出去. 在 okHttp 中分成 Application Interceptor 和 Network Interceptor 两种. A

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

    前言:前段时间在开发APP的时候,经常出现由于用户设备环境的原因,拿不到从网络端获取的数据,所以在APP端展现的结果总是一个空白的框,这种情况对于用户体验来讲是极其糟糕的,所以,苦思冥想决定对OKHTTP下手(因为我在项目中使用的网络请求框架就是OKHTTP),则 写了这么一个网络数据缓存拦截器. OK,那么我们决定开始写了,我先说一下思路: 思路篇 既然要写的是网络数据缓存拦截器,主要是利用了OKHTTP强大的拦截器功能,那么我们应该对哪些数据进行缓存呢,或者在哪些情况下启用数据进行缓存机制呢

  • 详解SpringMVC中使用Interceptor拦截器

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那样子判断当前时间是否是购票时间.  一.定义Interceptor实现类 SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的.在SpringMVC 中定义一个Interceptor 非常简单,主要有两种方式,第一种方式是要定义的Interce

  • Mybatis Interceptor 拦截器的实现

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件. 拦截器(Interceptor)在 Mybatis 中被当做插件(plugin)对待,官方文档提供了 Executor,ParameterHandler,ResultSetHandler,StatementHandler 共4种,并且提示"这些类中方法的细

  • springboot Interceptor拦截器excludePathPatterns忽略失效

    springboot Interceptor拦截器excludePathPatterns忽略失效 excludePathPatterns方法是排除访问路径,但是当你排除的url路径在项目中并不存在的时候,springboot会将路径编程/error,从而无法进行排除. 例如下面代码: registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/login&q

  • spring boot中interceptor拦截器未生效的解决

    目录 interceptor拦截器未生效 开始用的spring boot版本为1.5.6 解决方案 HandlerInterceptor实现登录失效拦截等 首先写一个实现HandlerInterceptor的类 然后把这个拦截器注册到spring中 interceptor拦截器未生效 搭建项目时发现拦截器未生效 开始用的spring boot版本为1.5.6 代码如下: @Configuration public class WebConfig extends WebMvcConfigurerA

  • Java interceptor拦截器的方法

    拦截器的概念 java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式.在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截.然后再之前或者之后加入某些操作.目前,我们需要掌握的主要是Spring的拦截器,Struts2的拦截器不用深究,知道即可. 拦截器的原理 大部分时候,拦截器方法都是通过代理的方式来调用的.Struts

  • 详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

    本文介绍Retrofit拦截器(Interceptor)的使用方法及相关注意事项.如果本文对您有所帮助,烦请点亮小红心- 首先看一下Interceptor源码: /** * Observes, modifies, and potentially short-circuits requests going out and the corresponding * responses coming back in. Typically interceptors add, remove, or tran

  • Spring interceptor拦截器配置及用法解析

    fifter.servlet.interceptor fifter用来处理请求头.请求参数.编码的一些设置,然后转交给servlet,处理业务,返回 servlet现在常用的spring,servlet拦截/到DispatcherServlet,交由spring管理 interceptor,servlet请求之后可以实现HandlerInterceptor做到preHandle.postHandle.afterCompletion在controller之前.之后.渲染之后 登陆 业务中常用的登陆

  • 详解flutter之网络请求dio,请求,拦截器简单示例

    flutter一直很火的网络请求插件dio 直接上代码,写成一个类,可以直接使用 包含请求的封装,拦截器的封装 import 'package:dio/dio.dart'; import 'dart:async'; import 'dart:io'; import './apidomain.dart'; import './httpHeaders.dart'; import 'package:shared_preferences/shared_preferences.dart'; class D

  • Asp.net Core 3.1基于AspectCore实现AOP实现事务、缓存拦截器功能

    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存. 这个也是网上说的面向切面编程AOP. AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面或后面添加代码,这很适合用于缓存.日志等处理. 在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore. 用起来非常非常的简单,但一

随机推荐