实现retrofit Post缓存拦截器(过期重新请求网络)

今天就聊聊retrofit 中使用自定义的缓存拦截器来实现POST的缓存策略吧。
带着这几个疑问吧:
1.retrofit 不是可以直接使用缓存拦截器吗?为什么还要自定义呢。
2.拦截器有什么作用呢,这个自定义的缓存拦截器实现什么功能呢。

解答:
1.retrofit内部使用的是okHttpClient,它内部只支持GET缓存,所以如果你想缓存POST的请求的话
还是得自己动手,丰衣足食了。
2.他实现这样的功能:
2.1 .默认不缓存,支持自定义每个接口设置缓存请求时间。
2.2 设置了缓存,则从缓存中读取数据,不访问网络,离线也能获取数据。过了缓存时间则再次从网 络获取数据并插入到离线缓存中。
2.3 缓存是存储在文件中用DiskLruCache实现。

先看看我们拦截器的代码 HttpCacheInterceptor.java:

package com.eebbk.bbkmiddlemarket.modulecommon.net.intercetor;

import android.text.TextUtils;

import com.eebbk.bbkmiddlemarket.modulebase.BaseApplication;
import com.eebbk.bbkmiddlemarket.modulebase.utils.JsonTools;
import com.eebbk.bbkmiddlemarket.modulebase.utils.LogUtil;
import com.eebbk.bbkmiddlemarket.modulecommon.CommonApplication;
import com.eebbk.bbkmiddlemarket.modulecommon.net.cache.DiskLruCacheHelper;
import com.eebbk.bbkmiddlemarket.modulecommon.data.DataCache;
import com.eebbk.bbkmiddlemarket.modulecommon.net.RetrofitClient;
import com.eebbk.bbkmiddlemarket.modulecommon.utils.MD5;
import com.eebbk.bbkmiddlemarket.modulecommon.utils.constants.HttpConstant;

import java.io.IOException;
import java.nio.charset.Charset;

import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;

public class HttpCacheInterceptor implements Interceptor {

private DiskLruCacheHelper helper;


public HttpCacheInterceptor() {
    try {
        helper = new DiskLruCacheHelper(BaseApplication.getContext());
    } catch (IOException e) {
        e.printStackTrace();
        LogUtil.e(RetrofitClient.LOG_TAG, "RetrofitClient  HttpCacheInterceptor 初始化DiskLruCacheHelp error   :" + e.getMessage());
    }
}


@Override
public Response intercept(Chain chain) throws IOException {


    Request request = chain.request();
    RequestBody requestBody = request.body();
    if (null == requestBody || CommonApplication.isDebugModeOpen()) {
        LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor debug 不缓存  :" + request.url());
        return chain.proceed(request);
    }
    long expiredTime = getExpiredTime(request);
    if (expiredTime == 0) {
        LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 默认不缓存 直接从网络中获取数据 ");
        return chain.proceed(request);
    } else {
        //获取请求url  和 参数
        String url = request.url().toString();
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        String params = buffer.readString(Charset.forName("UTF-8"));
        DataCache dataCache = getCacheByDiskLru(url, params);
        if (dataCache == null || TextUtils.isEmpty(dataCache.getJson()) || System.currentTimeMillis() > dataCache.getExpireTime()) {
            LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 需要缓存 当前无缓存记录或缓存过期 直接从网络中获取数据 ");
            Response response = chain.proceed(request);
            ResponseBody responseBody = response.body();
            if (null == responseBody) {
                return chain.proceed(request);
            }
            //获取MediaType,用于重新构建ResponseBody
            MediaType type = responseBody.contentType();
            //获取body字节即响应,用于存入数据库和重新构建ResponseBody
            byte[] bs = responseBody.bytes();
            response = response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    //重新构建body,原因在于body只能调用一次,之后就关闭了。
                    .body(ResponseBody.create(type, bs))
                    .build();
            //插入缓存数据库
            putCache(getCacheKey(url, params), expiredTime, bs);
            return response;
        } else {
            LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 从缓存中获取数据");
            return new Response.Builder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .body(ResponseBody.create(MediaType.parse("application/json"), dataCache.getJson().getBytes()))
                    .request(request)
                    .message("")
                    .protocol(Protocol.HTTP_1_1)
                    .code(200)
                    .build();
        }
    }
}


private DataCache getCacheByDiskLru(String url, String params) {
    DataCache dataCache = null;
    try {
        //获取请求缓存
        String cacheStr = helper.getAsString(getCacheKey(url, params));
        dataCache = JsonTools.jsonToObj(cacheStr, DataCache.class);
    } catch (Exception e) {
        LogUtil.e(RetrofitClient.LOG_TAG, " HttpCacheInterceptor getCacheByDiskLru error :" + e.getMessage());
    }
    return dataCache;
}


private long getExpiredTime(Request request) {
    long expiredTime = 0;
    try {
        String expiredTimeStr = request.header(HttpConstant.HEADER_EXPIRED_TIME);
        expiredTime = Long.valueOf(expiredTimeStr);
    } catch (NumberFormatException e) {
        LogUtil.e(RetrofitClient.LOG_TAG, "HttpCacheInterceptor getExpiredTime error :" + e.getMessage());
    }
    LogUtil.i(RetrofitClient.LOG_TAG, "HttpCacheInterceptor getExpiredTime  :" + expiredTime);
    return expiredTime;
}


private void putCache(String key, long expiredTime, byte[] bs) {
    try {
        DataCache netCache = new DataCache(System.currentTimeMillis() + expiredTime * 1000, new String(bs, "UTF-8"));
        helper.put(key, JsonTools.objToJson(netCache));
        LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 从网络中获取数据 插入缓存");
    } catch (Exception e) {
        LogUtil.e(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 插入数据 DataCache error :" + e.getMessage());
    }
}


/**
 * 根据Url 与 params 获取缓存Key
 *
 * @param url
 * @param params
 * @return cacheKey
 */
private String getCacheKey(String url, String params) {
    return MD5.md5(url + params);
}

}

讲下思路:
构造方法中,我初始化出DiskLruCacheHelper用来辅助存储网络缓存。
主要看看intercept(Chain chain)这个方法,每次请求都会经过这个方法。我们的缓存拦截就是要在这里实现。
1.首先进行安全校验,如果请求体为null或者是Debug模式(Debug模式是我自己写的逻辑)就直接返回,走正常访问网络流传。
2.通过getExpiredTime()方法获取请求体的缓存时间,如果未设置缓存时间,则走正常网络访问
3.如果设置了缓存时间,则从缓存中获取缓存对象DataCache,如果获取为Null,或者时间过期,则从网络中获取数据再插入本地缓存。
4.从缓存中获取的对象不为Null,则直接从本地缓存中获取数据。

我们这么用

初始化OkHttpClient的时候

new OkHttpClient.Builder().addInterceptor(new HttpCacheInterceptor()).build();

 
 
  • 1
在Service中,在请求头中设置缓存时间。

 
 
  • 1

@Headers(HttpConstant.HEADER_EXPIRED_TIME+":"+ HttpConstant.TIME_CACHE_TEN_MINUTE)**

@POST("businessLike/getHomePage")
@FormUrlEncoded
@Headers(HttpConstant.HEADER_EXPIRED_TIME+":"+ HttpConstant.TIME_CACHE_TEN_MINUTE)
Observable<BaseResponse<List<ChoicenessWithAdsBean>>> getHomePage(@FieldMap Map<String, String> maps);

 
 
  • 1
  • 2
  • 3
  • 4

HttpConstant.java

package com.eebbk.bbkmiddlemarket.modulecommon.utils.constants;

public class HttpConstant {

//expired_time 请求头
public static final String HEADER_EXPIRED_TIME = "expired_time";


/**----------------------------缓存时间  start--------------------------------**/


/**
 * 秒
 */
public static final int SECOND = 1;
/**
 * 分
 */
public static final int MINUTE = 60 * SECOND;
/**
 * 时
 */
public static final int HOUR = 60 * MINUTE;
/**
 * 天
 */
public static final int DAY = 24 * HOUR;
/**
 * 周
 */
public static final int WEEK = 7 * DAY;


/**
 * 缓存十分钟  60s x 10
 */
public static final int TIME_CACHE_TEN_MINUTE = 10 * MINUTE;


/**----------------------------缓存时间  end--------------------------------**/

}

再贴上我们几个缓存工具类
DiskLruCacheHelper.java

package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;

/**

  • Created by zhy on 15/7/28.
    */
    public class DiskLruCacheHelper {
    private static final String DIR_NAME = “diskCache”;
    private static final int MAX_COUNT = 5 * 1024 * 1024;
    private static final int DEFAULT_APP_VERSION = 1;

    /**

    • The default valueCount when open DiskLruCache.
      */
      private static final int DEFAULT_VALUE_COUNT = 1;

    private static final String TAG = “DiskLruCacheHelper”;

    private DiskLruCache mDiskLruCache;

    public DiskLruCacheHelper(Context context) throws IOException {
    mDiskLruCache = generateCache(context, DIR_NAME, MAX_COUNT);
    }

    public DiskLruCacheHelper(Context context, String dirName) throws IOException {
    mDiskLruCache = generateCache(context, dirName, MAX_COUNT);
    }

    public DiskLruCacheHelper(Context context, String dirName, int maxCount) throws IOException {
    mDiskLruCache = generateCache(context, dirName, maxCount);
    }

    //custom cache dir
    public DiskLruCacheHelper(File dir) throws IOException {
    mDiskLruCache = generateCache(null, dir, MAX_COUNT);
    }

    public DiskLruCacheHelper(Context context, File dir) throws IOException {
    mDiskLruCache = generateCache(context, dir, MAX_COUNT);
    }

    public DiskLruCacheHelper(Context context, File dir, int maxCount) throws IOException {
    mDiskLruCache = generateCache(context, dir, maxCount);
    }

    private DiskLruCache generateCache(Context context, File dir, int maxCount) throws IOException {
    if (!dir.exists() || !dir.isDirectory()) {
    throw new IllegalArgumentException(
    dir + " is not a directory or does not exists. ");
    }

      int appVersion = context == null ? DEFAULT_APP_VERSION : CacheUtils.getAppVersion(context);
    
    
      DiskLruCache diskLruCache = DiskLruCache.open(
              dir,
              appVersion,
              DEFAULT_VALUE_COUNT,
              maxCount);
    
    
      return diskLruCache;
    

    }

    private DiskLruCache generateCache(Context context, String dirName, int maxCount) throws IOException {
    DiskLruCache diskLruCache = DiskLruCache.open(
    getDiskCacheDir(context, dirName),
    CacheUtils.getAppVersion(context),
    DEFAULT_VALUE_COUNT,
    maxCount);
    return diskLruCache;
    }
    // =======================================
    // ============== String 数据 读写 =============
    // =======================================

    public void put(String key, String value) {
    DiskLruCache.Editor edit = null;
    BufferedWriter bw = null;
    try {
    edit = editor(key);
    if (edit == null) return;
    OutputStream os = edit.newOutputStream(0);
    bw = new BufferedWriter(new OutputStreamWriter(os));
    bw.write(value);
    edit.commit();//write CLEAN
    } catch (IOException e) {
    e.printStackTrace();
    try {
    //s
    edit.abort();//write REMOVE
    } catch (IOException e1) {
    e1.printStackTrace();
    }
    } finally {
    try {
    if (bw != null)
    bw.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    public String getAsString(String key) {
    InputStream inputStream = null;
    inputStream = get(key);
    if (inputStream == null) return null;
    String str = null;
    try {
    str = CacheUtils.readFully(new InputStreamReader(inputStream, CacheUtils.UTF_8));
    } catch (IOException e) {
    e.printStackTrace();
    try {
    inputStream.close();
    } catch (IOException e1) {
    e1.printStackTrace();
    }
    }
    return str;
    }

    public void put(String key, JSONObject jsonObject) {
    put(key, jsonObject.toString());
    }

    public JSONObject getAsJson(String key) {
    String val = getAsString(key);
    try {
    if (val != null)
    return new JSONObject(val);
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return null;
    }

    // =======================================
    // ============ JSONArray 数据 读写 =============
    // =======================================

    public void put(String key, JSONArray jsonArray) {
    put(key, jsonArray.toString());
    }

    public JSONArray getAsJSONArray(String key) {
    String JSONString = getAsString(key);
    try {
    JSONArray obj = new JSONArray(JSONString);
    return obj;
    } catch (Exception e) {
    e.printStackTrace();
    return null;
    }
    }

    // =======================================
    // ============== byte 数据 读写 =============
    // =======================================

    /**

    • 保存 byte数据 到 缓存中

    • @param key 保存的key

    • @param value 保存的数据
      */
      public void put(String key, byte[] value) {
      OutputStream out = null;
      DiskLruCache.Editor editor = null;
      try {
      editor = editor(key);
      if (editor == null) {
      return;
      }
      out = editor.newOutputStream(0);
      out.write(value);
      out.flush();
      editor.commit();//write CLEAN
      } catch (Exception e) {
      e.printStackTrace();
      try {
      editor.abort();//write REMOVE
      } catch (IOException e1) {
      e1.printStackTrace();
      }

      } finally {
      if (out != null) {
      try {
      out.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      }

    public byte[] getAsBytes(String key) {
    byte[] res = null;
    InputStream is = get(key);
    if (is == null) return null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
    byte[] buf = new byte[256];
    int len = 0;
    while ((len = is.read(buf)) != -1) {
    baos.write(buf, 0, len);
    }
    res = baos.toByteArray();
    } catch (IOException e) {
    e.printStackTrace();
    }
    return res;
    }

    // =======================================
    // ============== 序列化 数据 读写 =============
    // =======================================
    public void put(String key, Serializable value) {
    DiskLruCache.Editor editor = editor(key);
    ObjectOutputStream oos = null;
    if (editor == null) return;
    try {
    OutputStream os = editor.newOutputStream(0);
    oos = new ObjectOutputStream(os);
    oos.writeObject(value);
    oos.flush();
    editor.commit();
    } catch (IOException e) {
    e.printStackTrace();
    try {
    editor.abort();
    } catch (IOException e1) {
    e1.printStackTrace();
    }
    } finally {
    try {
    if (oos != null)
    oos.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    public <T> T getAsSerializable(String key) {
    T t = null;
    InputStream is = get(key);
    ObjectInputStream ois = null;
    if (is == null) return null;
    try {
    ois = new ObjectInputStream(is);
    t = (T) ois.readObject();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (ois != null)
    ois.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    return t;
    }

    // =======================================
    // ============== bitmap 数据 读写 =============
    // =======================================
    public void put(String key, Bitmap bitmap) {
    put(key, CacheUtils.bitmap2Bytes(bitmap));
    }

    public Bitmap getAsBitmap(String key) {
    byte[] bytes = getAsBytes(key);
    if (bytes == null) return null;
    return CacheUtils.bytes2Bitmap(bytes);
    }

    // =======================================
    // ============= drawable 数据 读写 =============
    // =======================================
    public void put(String key, Drawable value) {
    put(key, CacheUtils.drawable2Bitmap(value));
    }

    public Drawable getAsDrawable(String key) {
    byte[] bytes = getAsBytes(key);
    if (bytes == null) {
    return null;
    }
    return CacheUtils.bitmap2Drawable(CacheUtils.bytes2Bitmap(bytes));
    }

    // =======================================
    // ============= other methods =============
    // =======================================
    public boolean remove(String key) {
    try {
    key = CacheUtils.hashKeyForDisk(key);
    return mDiskLruCache.remove(key);
    } catch (IOException e) {
    e.printStackTrace();
    }
    return false;
    }

    public void close() throws IOException {
    mDiskLruCache.close();
    }

    public void delete() throws IOException {
    mDiskLruCache.delete();
    }

    public void flush() throws IOException {
    mDiskLruCache.flush();
    }

    public boolean isClosed() {
    return mDiskLruCache.isClosed();
    }

    public long size() {
    return mDiskLruCache.size();
    }

    public void setMaxSize(long maxSize) {
    mDiskLruCache.setMaxSize(maxSize);
    }

    public File getDirectory() {
    return mDiskLruCache.getDirectory();
    }

    public long getMaxSize() {
    return mDiskLruCache.getMaxSize();
    }

    // =======================================
    // ===遇到文件比较大的,可以直接通过流读写 =====
    // =======================================
    //basic editor
    public DiskLruCache.Editor editor(String key) {
    try {
    key = CacheUtils.hashKeyForDisk(key);
    //wirte DIRTY
    DiskLruCache.Editor edit = mDiskLruCache.edit(key);
    //edit maybe null :the entry is editing
    if (edit == null) {
    Log.w(TAG, “the entry spcified key:” + key + " is editing by other . ");
    }
    return edit;
    } catch (IOException e) {
    e.printStackTrace();
    }

      return null;
    

    }

    //basic get
    public InputStream get(String key) {
    try {
    DiskLruCache.Snapshot snapshot = mDiskLruCache.get(CacheUtils.hashKeyForDisk(key));
    if (snapshot == null) //not find entry , or entry.readable = false
    {
    Log.e(TAG, “not find entry , or entry.readable = false”);
    return null;
    }
    //write READ
    return snapshot.getInputStream(0);

      } catch (IOException e) {
          e.printStackTrace();
          return null;
      }
    

    }

    // =======================================
    // ============== 序列化 数据 读写 =============
    // =======================================

    private File getDiskCacheDir(Context context, String uniqueName) {
    String cachePath = context.getCacheDir().getPath();
    return new File(cachePath + File.separator + uniqueName);
    }
    }

DiskLruCache.java

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;

import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**

  • Created by lyj 2019-3-26
    DiskLruCache
    */
    public final class DiskLruCache implements Closeable {
    private static final String JOURNAL_FILE = “journal”;
    private static final String JOURNAL_FILE_TEMP = “journal.tmp”;
    private static final String JOURNAL_FILE_BACKUP = “journal.bkp”;
    private static final String MAGIC = “libcore.io.DiskLruCache”;
    private static final String VERSION_1 = “1”;
    private static final long ANY_SEQUENCE_NUMBER = -1;
    private static final String STRING_KEY_PATTERN = “[a-z0-9_-]{1,120}”;
    private static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
    private static final String CLEAN = “CLEAN”;
    private static final String DIRTY = “DIRTY”;
    private static final String REMOVE = “REMOVE”;
    private static final String READ = “READ”;

private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<>(0, 0.75f, true);
private int redundantOpCount;

/**

  • To differentiate between old and current snapshots, each entry is given
  • a sequence number each time an edit is committed. A snapshot is stale if
  • its sequence number is not equal to its entry’s sequence number.
    */
    private long nextSequenceNumber = 0;

/** This cache uses a single background thread to evict entries. */
final ThreadPoolExecutor executorService =
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};

private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}

/**

  • Opens the cache in {@code directory}, creating a cache if none exists
  • there.
  • @param directory a writable directory
  • @param valueCount the number of values per cache entry. Must be positive.
  • @param maxSize the maximum number of bytes this cache should use to store
  • @throws IOException if reading or writing the cache directory fails
    */
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
    throws IOException {
    if (maxSize <= 0) {
    throw new IllegalArgumentException(“maxSize <= 0”);
    }
    if (valueCount <= 0) {
    throw new IllegalArgumentException(“valueCount <= 0”);
    }
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
  File journalFile = new File(directory, JOURNAL_FILE);
  // If journal file also exists just delete backup file.
  if (journalFile.exists()) {
    backupFile.delete();
  } else {
    renameTo(backupFile, journalFile, false);
  }
}

// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
  try {
    cache.readJournal();
    cache.processJournal();
    return cache;
  } catch (IOException journalIsCorrupt) {
    System.out
        .println("DiskLruCache "
            + directory
            + " is corrupt: "
            + journalIsCorrupt.getMessage()
            + ", removing");
    cache.delete();
  }
}

// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;

}

private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), CacheUtils.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException(“unexpected journal header: [” + magic + ", " + version + ", "
+ valueCountString + ", " + blank + “]”);
}

  int lineCount = 0;
  while (true) {
    try {
      readJournalLine(reader.readLine());
      lineCount++;
    } catch (EOFException endOfJournal) {
      break;
    }
  }
  redundantOpCount = lineCount - lruEntries.size();

  // If we ended on a truncated line, rebuild the journal before appending to it.
  if (reader.hasUnterminatedLine()) {
    rebuildJournal();
  } else {
    journalWriter = new BufferedWriter(new OutputStreamWriter(
        new FileOutputStream(journalFile, true), CacheUtils.US_ASCII));
  }
} finally {
  CacheUtils.closeQuietly(reader);
}

}

private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(’ ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}

int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
  key = line.substring(keyBegin);
  if (firstSpace == REMOVE.length() &amp;&amp; line.startsWith(REMOVE)) {
    lruEntries.remove(key);
    return;
  }
} else {
  key = line.substring(keyBegin, secondSpace);
}

Entry entry = lruEntries.get(key);
if (entry == null) {
  entry = new Entry(key);
  lruEntries.put(key, entry);
}

if (secondSpace != -1 &amp;&amp; firstSpace == CLEAN.length() &amp;&amp; line.startsWith(CLEAN)) {
  String[] parts = line.substring(secondSpace + 1).split(" ");
  entry.readable = true;
  entry.currentEditor = null;
  entry.setLengths(parts);
} else if (secondSpace == -1 &amp;&amp; firstSpace == DIRTY.length() &amp;&amp; line.startsWith(DIRTY)) {
  entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 &amp;&amp; firstSpace == READ.length() &amp;&amp; line.startsWith(READ)) {
  // This work was already done by calling lruEntries.get().
} else {
  throw new IOException("unexpected journal line: " + line);
}

}

/**

  • Computes the initial size and collects garbage as a part of opening the
  • cache. Dirty entries are assumed to be inconsistent and will be deleted.
    */
    private void processJournal() throws IOException {
    deleteIfExists(journalFileTmp);
    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
    Entry entry = i.next();
    if (entry.currentEditor == null) {
    for (int t = 0; t < valueCount; t++) {
    size += entry.lengths[t];
    }
    } else {
    entry.currentEditor = null;
    for (int t = 0; t < valueCount; t++) {
    deleteIfExists(entry.getCleanFile(t));
    deleteIfExists(entry.getDirtyFile(t));
    }
    i.remove();
    }
    }
    }

/**

  • Creates a new journal that omits redundant information. This replaces the
  • current journal if it exists.
    */
    private synchronized void rebuildJournal() throws IOException {
    if (journalWriter != null) {
    journalWriter.close();
    }
Writer writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream(journalFileTmp), CacheUtils.US_ASCII));
try {
  writer.write(MAGIC);
  writer.write("\n");
  writer.write(VERSION_1);
  writer.write("\n");
  writer.write(Integer.toString(appVersion));
  writer.write("\n");
  writer.write(Integer.toString(valueCount));
  writer.write("\n");
  writer.write("\n");

  for (Entry entry : lruEntries.values()) {
    if (entry.currentEditor != null) {
      writer.write(DIRTY + ' ' + entry.key + '\n');
    } else {
      writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
    }
  }
} finally {
  writer.close();
}

if (journalFile.exists()) {
  renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();

journalWriter = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream(journalFile, true), CacheUtils.US_ASCII));

}

private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}

private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}

/**

  • Returns a snapshot of the entry named {@code key}, or null if it doesn’t
  • exist is not currently readable. If a value is returned, it is moved to
  • the head of the LRU queue.
    */
    public synchronized Snapshot get(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null) {
    return null;
    }
if (!entry.readable) {
  return null;
}

// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
  for (int i = 0; i &lt; valueCount; i++) {
    ins[i] = new FileInputStream(entry.getCleanFile(i));
  }
} catch (FileNotFoundException e) {
  // A file must have been deleted manually!
  for (int i = 0; i &lt; valueCount; i++) {
    if (ins[i] != null) {
      CacheUtils.closeQuietly(ins[i]);
    } else {
      break;
    }
  }
  return null;
}

redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
  executorService.submit(cleanupCallable);
}

return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);

}

/**

  • Returns an editor for the entry named {@code key}, or null if another
  • edit is in progress.
    */
    public Editor edit(String key) throws IOException {
    return edit(key, ANY_SEQUENCE_NUMBER);
    }

private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}

Editor editor = new Editor(entry);
entry.currentEditor = editor;

// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;

}

/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}

/**

  • Returns the maximum number of bytes that this cache should use to store
  • its data.
    */
    public synchronized long getMaxSize() {
    return maxSize;
    }

/**

  • Changes the maximum number of bytes the cache can store and queues a job
  • to trim the existing store, if necessary.
    */
    public synchronized void setMaxSize(long maxSize) {
    this.maxSize = maxSize;
    executorService.submit(cleanupCallable);
    }

/**

  • Returns the number of bytes currently being used to store the values in
  • this cache. This may be greater than the max size if a background
  • deletion is pending.
    */
    public synchronized long size() {
    return size;
    }

private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}

// If this edit is creating the entry for the first time, every index must have a value.
if (success &amp;&amp; !entry.readable) {
  for (int i = 0; i &lt; valueCount; i++) {
    if (!editor.written[i]) {
      editor.abort();
      throw new IllegalStateException("Newly created entry didn't create value for index " + i);
    }
    if (!entry.getDirtyFile(i).exists()) {
      editor.abort();
      return;
    }
  }
}

for (int i = 0; i &lt; valueCount; i++) {
  File dirty = entry.getDirtyFile(i);
  if (success) {
    if (dirty.exists()) {
      File clean = entry.getCleanFile(i);
      dirty.renameTo(clean);
      long oldLength = entry.lengths[i];
      long newLength = clean.length();
      entry.lengths[i] = newLength;
      size = size - oldLength + newLength;
    }
  } else {
    deleteIfExists(dirty);
  }
}

redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
  entry.readable = true;
  journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  if (success) {
    entry.sequenceNumber = nextSequenceNumber++;
  }
} else {
  lruEntries.remove(entry.key);
  journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();

if (size &gt; maxSize || journalRebuildRequired()) {
  executorService.submit(cleanupCallable);
}

}

/**

  • We only rebuild the journal when it will halve the size of the journal
  • and eliminate at least 2000 ops.
    */
    private boolean journalRebuildRequired() {
    final int redundantOpCompactThreshold = 2000;
    return redundantOpCount >= redundantOpCompactThreshold //
    && redundantOpCount >= lruEntries.size();
    }

/**

  • Drops the entry for {@code key} if it exists and can be removed. Entries
  • actively being edited cannot be removed.
  • @return true if an entry was removed.
    */
    public synchronized boolean remove(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null || entry.currentEditor != null) {
    return false;
    }
for (int i = 0; i &lt; valueCount; i++) {
  File file = entry.getCleanFile(i);
  if (file.exists() &amp;&amp; !file.delete()) {
    throw new IOException("failed to delete " + file);
  }
  size -= entry.lengths[i];
  entry.lengths[i] = 0;
}

redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);

if (journalRebuildRequired()) {
  executorService.submit(cleanupCallable);
}

return true;

}

/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}

private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException(“cache is closed”);
}
}

/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}

/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}

private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}

/**

  • Closes the cache and deletes all of its stored values. This will delete
  • all files in the cache directory including files that weren’t created by
  • the cache.
    */
    public void delete() throws IOException {
    close();
    CacheUtils.deleteContents(directory);
    }

private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException("keys must match regex "
+ STRING_KEY_PATTERN + “: “” + key + “””);
}
}

private static String inputStreamToString(InputStream in) throws IOException {
return CacheUtils.readFully(new InputStreamReader(in, CacheUtils.UTF_8));
}

/** A snapshot of the values for an entry. */
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private final long[] lengths;

private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
  this.key = key;
  this.sequenceNumber = sequenceNumber;
  this.ins = ins;
  this.lengths = lengths;
}

/**
 * Returns an editor for this snapshot's entry, or null if either the
 * entry has changed since this snapshot was created or if another edit
 * is in progress.
 */
public Editor edit() throws IOException {
  return DiskLruCache.this.edit(key, sequenceNumber);
}

/** Returns the unbuffered stream with the value for {@code index}. */
public InputStream getInputStream(int index) {
  return ins[index];
}

/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
  return inputStreamToString(getInputStream(index));
}

/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
  return lengths[index];
}

public void close() {
  for (InputStream in : ins) {
    CacheUtils.closeQuietly(in);
  }
}

}

private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override
public void write(int b) throws IOException {
// Eat all writes silently. Nom nom.
}
};

/** Edits the values for an entry. */
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;

private Editor(Entry entry) {
  this.entry = entry;
  this.written = (entry.readable) ? null : new boolean[valueCount];
}

/**
 * Returns an unbuffered input stream to read the last committed value,
 * or null if no value has been committed.
 */
public InputStream newInputStream(int index) throws IOException {
  synchronized (DiskLruCache.this) {
    if (entry.currentEditor != this) {
      throw new IllegalStateException();
    }
    if (!entry.readable) {
      return null;
    }
    try {
      return new FileInputStream(entry.getCleanFile(index));
    } catch (FileNotFoundException e) {
      return null;
    }
  }
}

/**
 * Returns the last committed value as a string, or null if no value
 * has been committed.
 */
public String getString(int index) throws IOException {
  InputStream in = newInputStream(index);
  return in != null ? inputStreamToString(in) : null;
}

/**
 * Returns a new unbuffered output stream to write the value at
 * {@code index}. If the underlying output stream encounters errors
 * when writing to the filesystem, this edit will be aborted when
 * {@link #commit} is called. The returned output stream does not throw
 * IOExceptions.
 */
public OutputStream newOutputStream(int index) throws IOException {
  if (index &lt; 0 || index &gt;= valueCount) {
    throw new IllegalArgumentException("Expected index " + index + " to "
            + "be greater than 0 and less than the maximum value count "
            + "of " + valueCount);
  }
  synchronized (DiskLruCache.this) {
    if (entry.currentEditor != this) {
      throw new IllegalStateException();
    }
    if (!entry.readable) {
      written[index] = true;
    }
    File dirtyFile = entry.getDirtyFile(index);
    FileOutputStream outputStream;
    try {
      outputStream = new FileOutputStream(dirtyFile);
    } catch (FileNotFoundException e) {
      // Attempt to recreate the cache directory.
      directory.mkdirs();
      try {
        outputStream = new FileOutputStream(dirtyFile);
      } catch (FileNotFoundException e2) {
        // We are unable to recover. Silently eat the writes.
        return NULL_OUTPUT_STREAM;
      }
    }
    return new FaultHidingOutputStream(outputStream);
  }
}

/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
  Writer writer = null;
  try {
    writer = new OutputStreamWriter(newOutputStream(index), CacheUtils.UTF_8);
    writer.write(value);
  } finally {
    CacheUtils.closeQuietly(writer);
  }
}

/**
 * Commits this edit so it is visible to readers.  This releases the
 * edit lock so another edit may be started on the same key.
 */
public void commit() throws IOException {
  if (hasErrors) {
    completeEdit(this, false);
    remove(entry.key); // The previous entry is stale.
  } else {
    completeEdit(this, true);
  }
  committed = true;
}

/**
 * Aborts this edit. This releases the edit lock so another edit may be
 * started on the same key.
 */
public void abort() throws IOException {
  completeEdit(this, false);
}

public void abortUnlessCommitted() {
  if (!committed) {
    try {
      abort();
    } catch (IOException ignored) {
    }
  }
}

private class FaultHidingOutputStream extends FilterOutputStream {
  private FaultHidingOutputStream(OutputStream out) {
    super(out);
  }

  @Override
  public void write(int oneByte) {
    try {
      out.write(oneByte);
    } catch (IOException e) {
      hasErrors = true;
    }
  }

  @Override
  public void write(byte[] buffer, int offset, int length) {
    try {
      out.write(buffer, offset, length);
    } catch (IOException e) {
      hasErrors = true;
    }
  }

  @Override
  public void close() {
    try {
      out.close();
    } catch (IOException e) {
      hasErrors = true;
    }
  }

  @Override
  public void flush() {
    try {
      out.flush();
    } catch (IOException e) {
      hasErrors = true;
    }
  }
}

}

private final class Entry {
private final String key;

/** Lengths of this entry's files. */
private final long[] lengths;

/** True if this entry has ever been published. */
private boolean readable;

/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;

/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;

private Entry(String key) {
  this.key = key;
  this.lengths = new long[valueCount];
}

public String getLengths() throws IOException {
  StringBuilder result = new StringBuilder();
  for (long size : lengths) {
    result.append(' ').append(size);
  }
  return result.toString();
}

/** Set lengths using decimal numbers like "10123". */
private void setLengths(String[] strings) throws IOException {
  if (strings.length != valueCount) {
    throw invalidLengths(strings);
  }

  try {
    for (int i = 0; i &lt; strings.length; i++) {
      lengths[i] = Long.parseLong(strings[i]);
    }
  } catch (NumberFormatException e) {
    throw invalidLengths(strings);
  }
}

private IOException invalidLengths(String[] strings) throws IOException {
  throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
}

public File getCleanFile(int i) {
  return new File(directory, key + "." + i);
}

public File getDirtyFile(int i) {
  return new File(directory, key + "." + i + ".tmp");
}

}
}

StrictLineReader.java

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

/**

  • Buffers input from an {@link InputStream} for reading lines.
  • <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
  • with “\n” or “\r\n”. End of input is reported by throwing {@code EOFException}. Unterminated
  • line at end of input is invalid and will be ignored, the caller may use {@code
  • hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
  • <p>This class is intended for reading input that strictly consists of lines, such as line-based
  • cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
  • with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
  • end-of-input reporting and a more restrictive definition of a line.
  • <p>This class supports only charsets that encode ‘\r’ and ‘\n’ as a single byte with value 13
  • and 10, respectively, and the representation of no other character contains these values.
  • We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
  • The default charset is US_ASCII.
    */
    class StrictLineReader implements Closeable {
    private static final byte CR = (byte) ‘\r’;
    private static final byte LF = (byte) ‘\n’;

private final InputStream in;
private final Charset charset;

/*

  • Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
  • and the data in the range [pos, end) is buffered for reading. At end of input, if there is
  • an unterminated line, we set end == -1, otherwise end == pos. If the underlying
  • {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
    */
    private byte[] buf;
    private int pos;
    private int end;

/**

  • Constructs a new {@code LineReader} with the specified charset and the default capacity.
  • @param in the {@code InputStream} to read data from.
  • @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
  • supported.
  • @throws NullPointerException if {@code in} or {@code charset} is null.
  • @throws IllegalArgumentException if the specified charset is not supported.
    */
    public StrictLineReader(InputStream in, Charset charset) {
    this(in, 8192, charset);
    }

/**

  • Constructs a new {@code LineReader} with the specified capacity and charset.
  • @param in the {@code InputStream} to read data from.
  • @param capacity the capacity of the buffer.
  • @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
  • supported.
  • @throws NullPointerException if {@code in} or {@code charset} is null.
  • @throws IllegalArgumentException if {@code capacity} is negative or zero
  • or the specified charset is not supported.
    */
    public StrictLineReader(InputStream in, int capacity, Charset charset) {
    if (in == null || charset == null) {
    throw new NullPointerException();
    }
    if (capacity < 0) {
    throw new IllegalArgumentException(“capacity <= 0”);
    }
    if (!(charset.equals(CacheUtils.US_ASCII))) {
    throw new IllegalArgumentException(“Unsupported encoding”);
    }
this.in = in;
this.charset = charset;
buf = new byte[capacity];

}

/**

  • Closes the reader by closing the underlying {@code InputStream} and
  • marking this reader as closed.
  • @throws IOException for errors when closing the underlying {@code InputStream}.
    */
    public void close() throws IOException {
    synchronized (in) {
    if (buf != null) {
    buf = null;
    in.close();
    }
    }
    }

/**

  • Reads the next line. A line ends with {@code “\n”} or {@code “\r\n”},

  • this end of line marker is not included in the result.

  • @return the next line from the input.

  • @throws IOException for underlying {@code InputStream} errors.

  • @throws EOFException for the end of source stream.
    */
    public String readLine() throws IOException {
    synchronized (in) {
    if (buf == null) {
    throw new IOException(“LineReader is closed”);
    }

    // Read more data if we are at the end of the buffered data.
    // Though it’s an error to read after an exception, we will let {@code fillBuf()}
    // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
    if (pos >= end) {
    fillBuf();
    }
    // Try to find LF in the buffered data and return the line if successful.
    for (int i = pos; i != end; ++i) {
    if (buf[i] == LF) {
    int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
    String res = new String(buf, pos, lineEnd - pos, charset.name());
    pos = i + 1;
    return res;
    }
    }

    // Let’s anticipate up to 80 characters on top of those already read.
    ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
    @Override
    public String toString() {
    int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
    try {
    return new String(buf, 0, length, charset.name());
    } catch (UnsupportedEncodingException e) {
    throw new AssertionError(e); // Since we control the charset this will never happen.
    }
    }
    };

    while (true) {
    out.write(buf, pos, end - pos);
    // Mark unterminated line in case fillBuf throws EOFException or IOException.
    end = -1;
    fillBuf();
    // Try to find LF in the buffered data and return the line if successful.
    for (int i = pos; i != end; ++i) {
    if (buf[i] == LF) {
    if (i != pos) {
    out.write(buf, pos, i - pos);
    }
    pos = i + 1;
    return out.toString();
    }
    }
    }
    }
    }

public boolean hasUnterminatedLine() {
return end == -1;
}

/**

  • Reads new input data into the buffer. Call only with pos == end or end == -1,
  • depending on the desired outcome if the function throws.
    */
    private void fillBuf() throws IOException {
    int result = in.read(buf, 0, buf.length);
    if (result == -1) {
    throw new EOFException();
    }
    pos = 0;
    end = result;
    }
    }

CacheUtils.java

package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
final class CacheUtils
{
static final Charset US_ASCII = Charset.forName(“US-ASCII”);
static final Charset UTF_8 = Charset.forName(“UTF-8”);

private CacheUtils()
{
}

static String readFully(Reader reader) throws IOException
{
    try
    {
        StringWriter writer = new StringWriter();
        char[] buffer = new char[1024];
        int count;
        while ((count = reader.read(buffer)) != -1)
        {
            writer.write(buffer, 0, count);
        }
        return writer.toString();
    } finally
    {
        reader.close();
    }
}

/**
 * Deletes the contents of {@code dir}. Throws an IOException if any file
 * could not be deleted, or if {@code dir} is not a readable directory.
 */
static void deleteContents(File dir) throws IOException
{
    File[] files = dir.listFiles();
    if (files == null)
    {
        throw new IOException("not a readable directory: " + dir);
    }
    for (File file : files)
    {
        if (file.isDirectory())
        {
            deleteContents(file);
        }
        if (!file.delete())
        {
            throw new IOException("failed to delete file: " + file);
        }
    }
}

static void closeQuietly(/*Auto*/Closeable closeable)
{
    if (closeable != null)
    {
        try
        {
            closeable.close();
        } catch (RuntimeException rethrown)
        {
            throw rethrown;
        } catch (Exception ignored)
        {
        }
    }
}

public static int getAppVersion(Context context)
{
    try
    {
        PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        return info.versionCode;
    } catch (PackageManager.NameNotFoundException e)
    {
        e.printStackTrace();
    }
    return 1;
}


public static String hashKeyForDisk(String key)
{
    String cacheKey;
    try
    {
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
        mDigest.update(key.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
    } catch (NoSuchAlgorithmException e)
    {
        cacheKey = String.valueOf(key.hashCode());
    }
    return cacheKey;
}

public static String bytesToHexString(byte[] bytes)
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i &lt; bytes.length; i++)
    {
        String hex = Integer.toHexString(0xFF &amp; bytes[i]);
        if (hex.length() == 1)
        {
            sb.append('0');
        }
        sb.append(hex);
    }
    return sb.toString();
}

public static byte[] bitmap2Bytes(Bitmap bm)
{
    if (bm == null)
    {
        return null;
    }
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
    return baos.toByteArray();
}

public static Bitmap bytes2Bitmap(byte[] bytes)
{
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}


/**
 * Drawable → Bitmap
 */
public static Bitmap drawable2Bitmap(Drawable drawable)
{
    if (drawable == null)
    {
        return null;
    }
    // 取 drawable 的长宽
    int w = drawable.getIntrinsicWidth();
    int h = drawable.getIntrinsicHeight();
    // 取 drawable 的颜色格式
    Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    // 建立对应 bitmap
    Bitmap bitmap = Bitmap.createBitmap(w, h, config);
    // 建立对应 bitmap 的画布
    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0, w, h);
    // 把 drawable 内容画到画布中
    drawable.draw(canvas);
    return bitmap;
}

/*
     * Bitmap → Drawable
	 */
@SuppressWarnings("deprecation")
public static Drawable bitmap2Drawable(Bitmap bm)
{
    if (bm == null)
    {
        return null;
    }
    BitmapDrawable bd = new BitmapDrawable(bm);
    bd.setTargetDensity(bm.getDensity());
    return new BitmapDrawable(bm);
}

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值