IT技术网www.itjs.cn

当前位置:首页 > 开发设计 > Android > 自己封装双缓存管理框架 Android 库

自己封装双缓存管理框架 Android 库

发布时间:2015-12-12 00:00 来源:Jamy Cai

一、概述

Android开发中,网络请求是很重要的一部分,而缓存网络请求来的图片或者响应结果字符串或者结果流,既可以省流量,同时也可以帮助我们解决无网或弱网情况下加载情况,当然也可以提升程序性能效率。纵所周知,缓存管理中肯定需要用到内存缓存,这里我们采用LruCache来管理内存的缓存。

LruCahce虽然速度快,但是只是内存级别的缓存,为了实现持久化的缓存,我们还需要文件级别的缓存,也就是说大家要把缓存保存到文件,而文件则是保存到手机存储或者SD卡存储中,即实现Disk级别的缓存,这里我们借助DiskLruCache这个辅助工具类来实现。顾名思义,这个工具类的作用就是使用Lru算法来存储信息到Disk上。

二、实例效果图

下面是个简单的实例演示效果图

自己封装双缓存管理框架 Android 库

三、缓存管理框架的实现解

1、内存缓存类的实现

该类主要实现内存级别的缓存管理类MemoryCache,使用LruCache来实现,因为无论是内存缓存还是Disk缓存,都需要读写操作,所以我们先抽象出一个缓存接口类:Cache接口:

public interface Cache {
    String get(final String key);
    void put(final String key, final String value);
    boolean remove(final String key);
}

然后,我们的内存缓存类MemoryCache需要实现Cache这个接口:

/**
 * 内存缓存类
 * Created by caizhiming on 2015/12/4.
 */
public class MemoryCache implements Cache {
    private LruCache<String, String> mMemoryLruCache;
    private EvictedListener mEvictedListener;

    public MemoryCache() {
        init();
    }

    public MemoryCache(EvictedListener listener) {
        init();
        this.mEvictedListener = listener;
    }

    public void setEvictedListener(EvictedListener listener) {
        this.mEvictedListener = listener;
    }

    public boolean hasEvictedListener() {
        return mEvictedListener != null;
    }

    private void init() {
        // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取可用内存空间的1/4作为缓存
        final int cacheSize = maxMemory / 4;
        mMemoryLruCache = new LruCache<String, String>(cacheSize) {
            @Override
            protected int sizeOf(String key, String value) {
                return value.getBytes().length;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, String oldValue, String newValue) {
                if (evicted) {
                    if (mEvictedListener != null) {
                        mEvictedListener.handleEvictEntry(key, oldValue);
                    }
                }
            }
        };
    }

    @Override
    public String get(String key) {
        return mMemoryLruCache.get(key);
    }

    @Override
    public void put(String key, String value) {
        mMemoryLruCache.put(key, value);
    }

    @Override
    public boolean remove(String key) {
        return Boolean.parseBoolean(mMemoryLruCache.remove(key));
    }

    /**
     * called when mMemoryLruCache evict entrys,
     * <p/>
     * using by CacheManager.Strategy.MEMORY_FIRST
     */
    public interface EvictedListener {
        void handleEvictEntry(String evictKey, String evictValue);
    }

2、文件级别的Disk缓存类实现

接下来我们需要写一个用于Disk缓存管理的类:DiskCache类,该类我们也实现Cache接口,该类的主要功能也是提供Disk缓存的读取和写入操作管理。

/**
 * Disk磁盘缓存类
 * Created by caizhiming on 2015/12/4.
 */
public class DiskCache implements Cache{

    private DiskLruCache mDiskLruCache = null;
    public DiskCache(Context context){
        init(context);
    }
    /**
     * 初始化 DiskLruCache
     */
    public void init(Context context){
        try {
            File cacheDir = getDiskCacheDir(context, "http_cache");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            Log.v("czm", "cache file=" + cacheDir.getAbsolutePath());
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public String get(String key) {
        String result = null;
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(hashKeyForDisk(key));
            if (snapShot != null) {
                result = snapShot.getString(0);
                return result;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return result;
    }

    @Override
    public void put(String key, String value) {
        DiskLruCache.Editor editor = null;
        try {
            editor = mDiskLruCache.edit(hashKeyForDisk(key));
            if (editor != null) {
                editor.set(0, value);
                editor.commit();
            }
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean remove(String key) {
        try {
            return mDiskLruCache.remove(hashKeyForDisk(key));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public Bitmap getImageCache(String key){
        Bitmap bitmap = null;
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(hashKeyForDisk(key));
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
    public void putImageCache(final String key){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyForDisk(key));
                    if (editor != null) {
                        OutputStream outputStream = editor.newOutputStream(0);
                        if (downloadUrlToStream(key, outputStream)) {
                            editor.commit();
                        } else {
                            editor.abort();
                        }
                    }
                    mDiskLruCache.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            CloseUtils.closeCloseable(out);
            CloseUtils.closeCloseable(in);
        }
        return false;
    }

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

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

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

}

3、搭建封装双缓存管理框架类XCCacheManager

有了上面的内存缓存类MemoryCache和Disk缓存类DiskCache,我们就可以搭建封装真正的缓存管理类XCCacheManager了。

(1) 首先我们采用线程池技术来实现多线程缓存的读写操作

这样可以提高程序的性能,同时能处理任务量比较大的并发读写操作。

private static XCCacheManager mInstance = null;

private Strategy mStrategy = Strategy.MEMORY_FIRST;
//线程池
private ExecutorService mExecutor = null;
//内存缓存
private MemoryCache mMemoryCache;
//Disk缓存
private DiskCache mDiskCache;

/**
     * 初始化 DiskLruCache
     */
    private void init(Context context) {
        mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        mDiskCache = new DiskCache(context);
        mMemoryCache = new MemoryCache();
    }

(2)其次XCCacheManager管理类采用单例实现

public static XCCacheManager getInstance(Context context, Strategy strategy) {
    if (mInstance == null) {
        synchronized (XCCacheManager.class) {
            if (mInstance == null) {
                mInstance = new XCCacheManager(context.getApplicationContext(), strategy);
            }
        }
    } else {
        mInstance.setStrategy(strategy);
    }
    return mInstance;
}

(3)缓存策略

这里我们定义了缓存策略,便于适应各种不同业务需求,可以灵活使用不同的策略

enum Strategy {
        MEMORY_ONLY(0), MEMORY_FIRST(1), DISK_ONLY(3);
        int id;

        Strategy(int id) {
            this.id = id;
        }
    }

默认采用内存优先MEMORY_FIRST这种策略

(4)根据对应的策略从缓存中读取内容

/**
     * 从缓存中读取value
     */
    public String readCache(final String key) {
        Future<String> ret = mExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String result = null;
                switch (mStrategy) {
                    case MEMORY_ONLY:
                        result = mMemoryCache.get(key);
                        break;
                    case MEMORY_FIRST:
                        result = mMemoryCache.get(key);
                        if (result == null) {
                            result = mDiskCache.get(key);
                        }
                        break;
                    case DISK_ONLY:
                        result = mDiskCache.get(key);
                        break;
                }
                return result;
            }
        });
        try {
            return ret.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

(5)将内容写入到缓存中

/**
     * 将value 写入到缓存中
     */
    public void writeCache(final String key, final String value) {
        mExecutor.submit(new Runnable() {
            @Override
            public void run() {
                switch (mStrategy) {
                    case MEMORY_FIRST:
                        if (!mMemoryCache.hasEvictedListener()) {
                            mMemoryCache.setEvictedListener(new MemoryCache.EvictedListener() {
                                @Override
                                public void handleEvictEntry(String evictKey, String evictValue) {
                                    mDiskCache.put(evictKey, evictValue);
                                }
                            });
                        }
                        mMemoryCache.put(key, value);
                        break;
                    case MEMORY_ONLY:
                        if (mMemoryCache.hasEvictedListener())
                            mMemoryCache.setEvictedListener(null);
                        mMemoryCache.put(key, value);
                        break;
                    case DISK_ONLY:
                        mDiskCache.put(key, value);
                        break;
                }
            }
        });
    }

到此为止,框架的开发到此完成。希望对有需要的人有所帮助。

四、源码下载

源码下载 : http://download.csdn.net/detail/jczmdeveloper/9348031

GitHub地址:https://github.com/jczmdeveloper/XCCacheManager