欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-02-21 17:34:22
前言:前段时间在开发app的时候,经常出现由于用户设备环境的原因,拿不到从网络端获取的数据,所以在app端展现的结果总是一个空白的框,这种情况对于用户体验来讲是极其糟糕的,...

前言:前段时间在开发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'}
  }
}

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

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

项目截图

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地址

看效果:演示图

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

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

结尾

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

demo地址:https://github.com/xiaolei123/okhttpcacheinterceptor

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