1、CacheManager缓存管理类,该类用Map缓存了文件目录及该目录对应的缓存管理器key:dirPath value:DiskCache
为了节省内存,把缓存写入同一个目录,这样Map只会缓存一个键值对
package xxx.cache;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 文件缓存管理
* Created by xxx on 2018/3/20
*/
public class CacheManager {
private static CacheManager mCacheManager;
//每个文件夹下对应一个单例管理类,统一文件操作入口,避免多个入口导致并发问题
private static Map<String, DiskCache> mControllMap = new HashMap<>();
private CacheManager() {}
public static CacheManager getInstance() {
if (mCacheManager == null) {
synchronized (CacheManager.class) {
if (mCacheManager == null) {
mCacheManager = new CacheManager();
}
}
}
return mCacheManager;
}
public DiskCache getDiskCache(String cacheDir, long maxSize) {
if (TextUtils.isEmpty(cacheDir)) {
throw new RuntimeException("cacheDir is null");
}
if (maxSize == 0) {
throw new RuntimeException("max size must > 0");
}
String key = cacheDir + myPid();
DiskCache controll = mControllMap.get(key);
if (controll == null) {
controll = DiskCache.getInstance(cacheDir, maxSize);
mControllMap.put(key, controll);
}
return controll;
}
public DiskCache getDiskCache() {
String key = FileCache.FILE_DIR + myPid();
DiskCache controll = mControllMap.get(key);
if (controll == null) {
controll = DiskCache.getInstance(FileCache.FILE_DIR, AutoClearController.MAX_SIZE);
mControllMap.put(key, controll);
}
return controll;
}
private String myPid() {
return "_" + android.os.Process.myPid();
}
}
2、DiskCache某个目录对应的缓存管理器,包含自动清理控制器AutoClearController、异步文件操作器AsyncFileCache
package xxx.cache;
import java.io.File;
import java.io.Serializable;
import xxx.common.utils.XLogUtil;
/**
* 1、记录文件大小,达到上限时删除最老的文件
* 2、文件读写
* Created by xxx on 2018/3/19
*/
public class DiskCache {
private static final String TAG = "DiskCache";
private static DiskCache mDiskCache;
private AutoClearController mAutoClearController;
private AsyncFileCache mAsyncFileCache;
private DiskCache() {
}
private DiskCache(String cacheDir, long maxSize) {
mAsyncFileCache = new AsyncFileCache(cacheDir);
mAutoClearController = new AutoClearController(cacheDir, maxSize) {
@Override
boolean deleteFile(File file) {
XLogUtil.d(TAG, "~~~~deleteFile " + file == null ? null : file.getAbsolutePath() + "~~~~");
return mAsyncFileCache != null && mAsyncFileCache.deleteFile(file);
}
};
mAsyncFileCache.setAutoClearController(mAutoClearController);
}
public static DiskCache getInstance(String cacheDir, long maxSize) {
if (mDiskCache == null) {
synchronized (DiskCache.class) {
if (mDiskCache == null) {
mDiskCache = new DiskCache(cacheDir, maxSize);
}
}
}
return mDiskCache;
}
public void setAutoClearEnable(boolean enable) {
if (mAutoClearController != null) {
mAutoClearController.setAutoClearEnable(enable);
}
XLogUtil.d(TAG, "~~~~setAutoClearEnable: " + enable + "~~~~");
}
public void put(String key, byte[] value) {
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncPut(key, value);
}
XLogUtil.d(TAG, "~~~~put: " + key + "~~~~");
}
public void putObject(String key, Serializable value) {
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncPutObject(key, value);
}
XLogUtil.d(TAG,"~~~~putObject: " + key + "~~~~");
}
public void get(String key, AsyncCallback callback) {
if (callback == null) {
return;
}
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncGet(key, callback);
}
XLogUtil.d(TAG, "~~~~get: " + key + "~~~~");
}
public void getObject(String key, AsyncCallback callback) {
if (callback == null) {
return;
}
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncGetObject(key, callback);
}
XLogUtil.d(TAG,"~~~~getObject: " + key + "~~~~");
}
public void remove(String key) {
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncRemove(key);
}
XLogUtil.d(TAG,"~~~~remove: " + key + "~~~~");
}
public void clear() {
if (mAsyncFileCache != null) {
mAsyncFileCache.asyncClear();
}
XLogUtil.d(TAG,"~~~~clear~~~~");
}
}
3、AutoClearController缓存自动清理控制器,里面记录该目录文件及文件最后修改时间、及总文件大小
当写入文件时,判断是否超过缓存大小限制,超过则从最老的文件开始清理
package xxx.cache;
import android.text.TextUtils;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import xxx.common.controller.ThreadPoolFactory;
/**
* 扫描缓存目录,当缓存超过上限时,从lastModify最小的开始删除,直到缓存文件低于大小限制
* Created by xxx on 2018/3/20
*/
abstract class AutoClearController {
public static final long MAX_SIZE = 50 * 1024 * 1024;//默认一个文件夹下缓存上限为50M
private final Map<File, Long> mLastUseDateMap = Collections.synchronizedMap(new HashMap<File, Long>());
private AtomicLong mCacheSize;
private long mMaxSize;
private boolean mAutoClear;
protected File mCacheDir;
public void setAutoClearEnable(boolean enable) {
this.mAutoClear = enable;
}
private AutoClearController() {}
public AutoClearController(String cacheDir, long maxSize) {
if (!mAutoClear) {
return;
}
this.mMaxSize = maxSize;
mCacheSize = new AtomicLong();
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (TextUtils.isEmpty(cacheDir)) {
throw new IllegalArgumentException("directory is null");
}
mCacheDir = new File(cacheDir);
if (!mCacheDir.exists() || !mCacheDir.isDirectory()) {
mCacheDir.mkdirs();
}
computeSizeAndCount();
}
/**
* 计算 cacheSize和cacheCount
*/
private synchronized void computeSizeAndCount() {
ThreadPoolFactory.instance().fixExecutor(() -> {
File[] cacheFiles = mCacheDir.listFiles();
if (cacheFiles == null || cacheFiles.length == 0) {
return;
}
int size = 0;
for (File file : cacheFiles) {
if (file == null || !file.exists()) {
continue;
}
if (file.isDirectory()) {//文件夹就干掉好了
deleteFile(file);
continue;
}
size += getFileSize(file);
mLastUseDateMap.put(file, file.lastModified());
}
mCacheSize.set(size);
});
}
public synchronized void put(File file) {
if (!mAutoClear) {
return;
}
long fileSize = getFileSize(file);
if (fileSize > mMaxSize) {
return;
}
long cacheSize = getCacheSize();
while (cacheSize + fileSize > mMaxSize) {
long freedSize = removeOldestFile();
cacheSize = addCacheSize(-freedSize);
}
addCacheSize(fileSize);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
mLastUseDateMap.put(file, currentTime);
}
public synchronized void get(File file) {
if (!mAutoClear) {
return;
}
if (file == null || !file.exists()) {
return;
}
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
mLastUseDateMap.put(file, currentTime);
}
public synchronized boolean remove(File file) {
if (!mAutoClear) {
return false;
}
if (file == null || !file.exists()) {
return true;//文件不存在,就当是删除成功好了
}
Iterator<Map.Entry<File,Long>> iterator = mLastUseDateMap.entrySet().iterator();
if (iterator.hasNext()) {
Map.Entry<File,Long> entry = iterator.next();
if (entry != null && entry.getKey() == file) {
iterator.remove();
return true;
}
}
return false;
}
public synchronized void clear() {
if (!mAutoClear) {
return;
}
mLastUseDateMap.clear();
mCacheSize.set(0);
}
/**
* 移除旧的文件
*/
private long removeOldestFile() {
if (mLastUseDateMap.isEmpty()) {
return 0;
}
Long oldestMills = null;
File oldestFile = null;
Iterator<Map.Entry<File, Long>> iterator = mLastUseDateMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<File, Long> entry = iterator.next();
File file = entry.getKey();
if (file == null) {
continue;
}
if (oldestFile == null) {
oldestFile = entry.getKey();
oldestMills = entry.getValue();
} else {
Long lastValueUsage = entry.getValue();
if (lastValueUsage < oldestMills) {
oldestMills = lastValueUsage;
oldestFile = entry.getKey();
}
}
}
if (oldestFile == null || !oldestFile.exists()) {
return 0;
}
if (deleteFile(oldestFile)) {
mLastUseDateMap.remove(oldestFile);
}
return getFileSize(oldestFile);
}
private long getFileSize(File file) {
if (file == null || !file.exists()) {
return 0;
}
return file.length();
}
/**
* 删除文件的具体操作抛出去,使得自动清理与文件读写管理使用同一个同步的删除方法,避免并发问题
* @param file
* @return
*/
abstract boolean deleteFile(File file);
private long addCacheSize(long size) {
mCacheSize.addAndGet(size);
if (mCacheSize.get() < 0) {
mCacheSize.set(0);
}
return mCacheSize.get();
}
private long getCacheSize() {
long size = mCacheSize.get();
return size >= 0 ? size : 0;
}
}
4、AsyncFileCache文件异步操作类,异步操作文件的读、写、删,里面的线程池代码就不贴了,自己写一个就好。
package xxx.cache;
import java.io.Serializable;
import java.lang.reflect.Type;
import xxx.common.controller.ThreadPoolFactory;
/**
* 异步写入、删除、清空
* Created by xxx on 2018/3/20
*/
final class AsyncFileCache<T> extends FileCache {
public AsyncFileCache(String dirPath) {
super(dirPath);
}
public void asyncPut(final String key, final byte[] value) {
if (value == null) {
return;
}
ThreadPoolFactory.instance().fixExecutor(() -> put(key, value));
}
public void asyncRemove(final String key) {
ThreadPoolFactory.instance().fixExecutor(() -> remove(key));
}
public void asyncClear() {
ThreadPoolFactory.instance().fixExecutor(() -> clear());
}
public void asyncGet(String key, AsyncCallback<T> callback) {
if (callback == null) {
return;
}
Type t = callback.getType();
Object object = get(key);
if (object == null || !(object.getClass().equals(t))) {
return;
}
ThreadPoolFactory.instance().fixExecutor(() -> callback.onResult((T)object));
}
public void asyncPutObject(final String key, final Serializable value) {
if (value == null) {
return;
}
ThreadPoolFactory.instance().fixExecutor(() -> putObject(key, value));
}
public void asyncGetObject(final String key, AsyncCallback<T> callback) {
if (callback == null) {
return;
}
Type t = callback.getType();
Object object = getObject(key);
if (object == null || !(object.getClass().equals(t))) {
return;
}
ThreadPoolFactory.instance().fixExecutor(() -> callback.onResult((T)object));
}
}
5、FileCache同步操作文件的读、写、删
package xxx.common.cache;
import android.content.Context;
import android.text.TextUtils;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import xxx.MyApplication;
import xxx.common.utils.XLogUtil;
/**
* 文件读写
* Created by xxx on 2018/3/19
*/
class FileCache {
private static final String TAG = "FileCache";
public static final String FILE_DIR = "FileCache";
private File mDirFile;
private DiskCacheWriteLocker mWriteLocker = new DiskCacheWriteLocker();
private AutoClearController mAutoClearController;
public void setAutoClearController(AutoClearController controller) {
mAutoClearController = controller;
}
public FileCache(String dirPath) {
if (TextUtils.isEmpty(dirPath)) {
dirPath = FILE_DIR;
}
mDirFile = getFilesDir(dirPath);
}
/**
* 获取默认文件存储路径
* @return 路径:Android/data/files/childDir
*/
private File getFilesDir(String childDir) {
Context context = MyApplication.getInstance();
File baseDir = context.getExternalFilesDir(childDir);
if (baseDir == null) {
baseDir = new File(context.getFilesDir(), childDir);
}
if (!baseDir.exists()) {
baseDir.mkdirs();
}
XLogUtil.d(TAG, "-------- cache directionary is: " + baseDir.getAbsolutePath() + "---------");
return baseDir;
}
public File getFile(String hash) {
return new File(mDirFile, hash);
}
public boolean put(String key, byte[] value) {
mWriteLocker.acquire(key);
File f = getFile(key);
OutputStream fos = null;
try {
fos = new BufferedOutputStream(new FileOutputStream(f));
fos.write(value);
fos.flush();
if (mAutoClearController != null) {
mAutoClearController.put(f);
}
return true;
} catch (IOException e) {
deleteFile(f);
return false;
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mWriteLocker.release(key);
}
}
public byte[] get(String key) {
File f = getFile(key);
if (f.exists()) {
try {
if (mAutoClearController != null) {
mAutoClearController.get(f);
}
FileInputStream fin = new FileInputStream(f);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len = 0;
while ((len = fin.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
fin.close();
return outStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public byte[] get(String key, long time) {
File f = getFile(key);
if (!f.exists()) {
return null;
}
if (time == -1 || System.currentTimeMillis() - f.lastModified() < time) {
return get(key);
}
return null;
}
public void clear() {
if (mAutoClearController != null) {
mAutoClearController.clear();
}
deleteFile(mDirFile);
}
public void remove(String key) {
File f = getFile(key);
if (mAutoClearController != null) {
mAutoClearController.remove(f);
}
deleteFile(f);
}
public boolean putObject(String key, Serializable value) {
if (key == null || null == value) {
return false;
}
return put(key, CacheUtil.toByteArray(value));
}
public Object getObject(String key) {
if (key == null) {
return null;
}
byte[] bytes = get(key);
if (bytes == null) {
return null;
}
return CacheUtil.toObject(bytes);
}
public Object getObject(String key, long time) {
File f = new File(mDirFile, key);
if (!f.exists()) {
return null;
}
if (time == -1 || System.currentTimeMillis() - f.lastModified() < time) {
return getObject(key);
}
return null;
}
public synchronized boolean deleteFile(File file) {
if (file == null || !file.exists()) {
return true;
}
if (file.isFile()) {
return file.delete();
}
boolean success = true;
if (file.isDirectory()) {
File[] childFiles = file.listFiles();
if (childFiles == null || childFiles.length == 0) {
if (!file.delete()) {
success = false;
}
}
for (int i = 0; i < childFiles.length; i++) {
if (!deleteFile(childFiles[i])) {
success = false;
}
}
if (!file.delete()) {
success = false;
}
}
return success;
}
}
6、DiskCacheWriteLocker文件写入锁
package xxx.common.cache;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 文件缓存写入锁
* Created by xxx on 2018/3/19
*/
final class DiskCacheWriteLocker {
private final Map<String, WriteLock> locks = new HashMap<>();
private final WriteLockPool writeLockPool = new WriteLockPool();
void acquire(String key) {
WriteLock writeLock;
synchronized (this) {
writeLock = locks.get(key);
if (writeLock == null) {
writeLock = writeLockPool.obtain();
locks.put(key, writeLock);
}
writeLock.interestedThreads++;
}
writeLock.lock.lock();
}
void release(String key) {
WriteLock writeLock;
synchronized (this) {
writeLock = locks.get(key);
if (writeLock == null || writeLock.interestedThreads <= 0) {
throw new IllegalArgumentException(
"Cannot release a lock that is not held" + ", key: " + key + ", interestedThreads: "
+ (writeLock == null ? 0 : writeLock.interestedThreads));
}
if (--writeLock.interestedThreads == 0) {
WriteLock removed = locks.remove(key);
if (!removed.equals(writeLock)) {
throw new IllegalStateException("Removed the wrong lock"
+ ", expected to remove: " + writeLock
+ ", but actually removed: " + removed
+ ", key: " + key);
}
writeLockPool.offer(removed);
}
}
writeLock.lock.unlock();
}
private static class WriteLock {
final Lock lock = new ReentrantLock();
int interestedThreads;
}
private static class WriteLockPool {
private static final int MAX_POOL_SIZE = 10;
private final Queue<WriteLock> pool = new ArrayDeque<>();
WriteLock obtain() {
WriteLock result;
synchronized (pool) {
result = pool.poll();
}
if (result == null) {
result = new WriteLock();
}
return result;
}
void offer(WriteLock writeLock) {
synchronized (pool) {
if (pool.size() < MAX_POOL_SIZE) {
pool.offer(writeLock);
}
}
}
}
}
7、byte[]与Object转换工具类,写这个类是因为直接把对象写入文件,读取时会跑EOFException,据说是读完后再读时没东西读了,抛出的异常,转byte[]后写入、读取就没这问题了
final class CacheUtil {
/**
* 对象转数组
* @param obj
* @return
*/
public static byte[] toByteArray (Object obj) {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray ();
oos.close();
bos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return bytes;
}
/**
* 数组转对象
* @param bytes
* @return
*/
public static Object toObject (byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream (bytes);
ObjectInputStream ois = new ObjectInputStream (bis);
obj = ois.readObject();
ois.close();
bis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return obj;
}
}
8、异步操作的回调函数
public class AsyncCallback<T> {
public void onResult(byte[] bytes) {
}
public void onResult(T object) {
}
public Type getType() {
Type superClass = getClass().getGenericSuperclass();
return ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}
9、线程池
public class ThreadPoolFactory {
private final static int FIX_THREAD_COUNT = 3;
private final static int FIX_NET_THREAD_COUNT = 5;
private volatile static ThreadPoolFactory instance;
private Executor fixExecutor;
private Executor netFixExecutor;
private Executor singleExecutor;
private Executor immediateExecutor;
private ThreadPoolFactory() {
fixExecutor = Executors.newFixedThreadPool(FIX_THREAD_COUNT);
netFixExecutor = Executors.newFixedThreadPool(FIX_NET_THREAD_COUNT);
singleExecutor = Executors.newSingleThreadExecutor();
immediateExecutor = Executors.newCachedThreadPool();
}
public static ThreadPoolFactory instance() {
if (instance == null) {
synchronized (ThreadPoolFactory.class) {
if (instance == null) {
instance = new ThreadPoolFactory();
}
}
}
return instance;
}
/**
* 用于可以尽快处理完的任务,比如数据库操作等。
* @param runnable
*/
public void fixExecutor(Runnable runnable) {
fixExecutor.execute(runnable);
}
/**
* 用于比较耗时的同步网络请求等任务
* @param runnable
*/
public void netFixExecutor(Runnable runnable) {
netFixExecutor.execute(runnable);
}
/**
* 处理需要单线程单任务。⚠️ 不要做特别耗时操作
* @param runnable
*/
public void singleExecutor (Runnable runnable) {
singleExecutor.execute(runnable);
}
public void executeImmediately(Runnable runnable){
immediateExecutor.execute(runnable);
}
}