Android中的缓存策略

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/qq_41979349/article/details/84036821

缓存策略的主要流程:

当程序第一次从网络加载图片后,将其缓存到储存设备上,下一次就不用再次从网络上获取了。为了提高应用的用户体验,往往还会再内存中再缓存一份,这样当应用打算从网络请求一张图片时,首先从内存中读取,如果没有那就从储存设备中获取,如果储存设备也没有,那就从网络上下载这张图片。因为从内存中加载图片比储存设备加载要快,所以这样既提高程序的效率又为用户节约了不必要的流量开销。而这种缓存策略不仅仅适用于图片,也适用于其他文件类型。

缓存算法

目前常用的一种缓存算法是LRU(Least Recently Used),LRU是近期最少使用的算法,核心思想:当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LruCacheDiskLruCache,LruCache用于实现内存缓存,而DiskLruCaChe则充当了存储设备缓存。

LruCache

LruCache是一个泛型类,内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象。并且提供了get和put方法来完成缓存的获取和添加操作。

  • 强引用:直接的对象引用
  • 软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
  • 弱引用:当一个对象只有弱引用存在时,此对象会被随时gc回收

初始化LruCaChe

int MaxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize){
    @override
    protected int sizeof(String key,Bitmap bitmap){
        return bitmap.getRowBytes()*bitmap.getHeight() / 1024;
    }
}

在上面代码中,只需要提供缓存的总容量大小并重写sizeof方法即可。另外一些特殊情况下,还需要重写LruCache的entryRemoved方法,LruCache移除旧缓存时会调用entryRemoved方法,因此可以在entryRemoved中完成一些资源回收工作。

从LruCache中获取一个缓存对象

mMemoryCache.get(key);

向LruCache中添加一个缓存对象

mMemoryCache.put(key,bitmap)

LruCache还支持删除操作,通过remove方法即可删除一个指定的缓存对象。

DiskLruCache

1.DiskLruCache的创建

DiskLruCache提供了open方法创建

public static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize);

open方法有四个参数,其中第一个参数为磁盘缓存在文件系统中的储存路径。缓存可以选择SD卡上的缓存目录,具体为:***/sdcard/Android/data/package_name/cache***目录,当应用被卸载后,包名的目录会一并被删除。当然还可以选择sd卡上的其他指定路径,还可以选择data下的当前应用的目录。这里一般有个原则:如果应用卸载后就希望删除缓存文件,那么就选择SD卡上的缓存目录,如果希望保留缓存数据那就应该选择sd卡上的其他路径

第二个参数为版本号,一般设为1.当版本号改变时会清空之前的所以缓存文件。

第三个参数表示单个节点所对应的数据的个数,一般设为1即可。第四个参数为缓存的总大小。下面是典型的DiskLruCache的创建过程:

private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50 ;//50Mb
File diskCacheFile = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheFile.exists()){
    dislCacheFile.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCaCheFile,1,1,DISK_CACHE_SIZE);

2.DiskLruCache的缓存添加

DiskLruCache的缓存添加的操作是通过Editor完成的,Editor表示一个缓存对象的编辑对象。这里以照片缓存为例子,首先获取图片的URL所对应的key,然后根据key就可以通过edit()来获取Editor对象,如果这个缓存正在被编辑,那么edit()会返回null,即DiskLruCache不允许同时编辑一个对象。之所以要把url转化成key,是因为图片的url很可能含有特殊的字符,这会影响url在安卓中直接使用,一般采用url的md5值作为key;

private String hashKeyFormUrl(String url){
    String cacheKey;
    try{
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
        mDigest.update(url.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
    } catch (NoSuchAlgorithmException e) {
        cacheKey = String.valueof(url.hashcode());
    }
    return cacheKey;
}

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

将图片的url转成key后,就可以获得Editor对象了。对于key来说,如果当前不存在其他的Editor对象,那么edit()就会返回一个新的Editor对象,通过他可以获得文件输出流。需注意前面的diskLruCache的open方法中设置了一个节点只有一个数据,因此下面的DISK_CACHE_INDEX常量直接设置成0即可。

String key = hashkeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null) {
    OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
}

有了文件输出流,那么当从网络下载图片时,图片就可以通过这个文件输出流写入到文件系统上,具体实现过程如下:

public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
    HttpURLConnection urlConnection = null;
    BufferedOutputStream out = null;
    BufferedInputSream in = null;
    
    try {
        final URL url = new URL(urlString);
        urlConnection = (HttpURLConnection) url.openConnection();
        in = new BufferedInputStream(urlConnection.getInputSream(),IO_BUFFER_SIZE);
        out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
        
        int b;
        while ((b = in.read())!= -1) {
            out.write(b);
        }
        return ture;
    } catch (IOException e) {
        e.printStack();;
    }finally {
        if(urlConnection != null) {
            urlConnection.disConnect();
        }
        MyUtils.close(in);
        MyUtils.close(out);
    }
    return false;
}

另外,必须通过Editor的commit()来提交写入操作,如果照片下载过程发生了异常,还可以通过Editor的abort()来回退整个操作。

OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlTOSream(url,outputStream)) {
    editor.commmit();
} else {
    editor.abort();
}
mDiskLruCache.flush();

3.DiskLruCache的缓存查找

缓存查找的过程首先需要将url转化成key,然后通过DiskLruCache的get方法得到一个Snapshot对象,再接着通过这个对象即可获得缓存文件的输入流。进而得到Bitmap对象。为了避免加载图片导致的OOM,一般不建议直接加载原图。上文提到使用BitmapFactory.Options来加载缩放后的图片,但是这种方法之前也提到对FileInputSream存在问题,原因是FileOutput是一种有序的文件流,两次的decodeStream调用会影响文件流的位置属性,导致第二次调用decodeStream时返回了null.故为了解决这种问题,首先得获得文件描述符,再通过BitmapFactory.decodeFileDecriptor方法加载一张缩放后的照片。

Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DislLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if(snapShot != null) {
    FileInputStream fileInputStream = (FileInputStream) snapShot.getInputSream(DISK_CACHE_INDEXT);
    FileDecriptor fileDecriptor = fileInputStream.getFD();
    bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDecriptor,reqWidth,reqHeight);
    if(bitmap != null) {
        addBitmapToMemoryCache(key,bitmap);
    }
}

注:本笔记来源于《Android开发艺术探索》相关知识

猜你喜欢

转载自blog.csdn.net/qq_41979349/article/details/84036821
今日推荐