Android文件缓存管理

这篇博客详细介绍了Android应用中实现的文件缓存管理系统,包括CacheManager缓存管理类、DiskCache目录缓存管理器、AutoClearController自动清理控制器,以及异步文件操作和锁机制。AutoClearController负责监测并清理超出大小限制的缓存文件,确保内存有效管理。AsyncFileCache实现了文件的异步读写删除,配合线程池优化性能。此外,还讨论了对象到byte[]转换的必要性,以避免读取异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

gitHub地址

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);
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值