首页 > 编程语言 > Android开发 > Android中的缓存
2016
10-18

Android中的缓存

为什么会用到缓存呢?主要是流量耗不起啊,国内的公共场所的WiFi的普及率不高,因此必须考虑流量的问题,说白了,就是用户体验啊,每次都网络请求,消耗资源不说,网速不好的情况下还会有网络延时,用户体验不好。

Android中的缓存,从方式上来说,一般有网络缓存,磁盘缓存即SD卡缓存,内存缓存。网络缓存需要服务端的配合,用于加快网络请求的响应速度。磁盘缓存一般用DiskLruCache,当然也可以用SqlLite数据库,以及sharedpreference等作持久化处理。这里主要说下两种常用的缓存方法,LruCache、DiskLruCache。前者用于内存缓存,后者用于设备缓存,一般两者结合起来效果更好。

其实缓存的实现并不难,每一中缓存都会有三个基本操作,添加、获取、删除。了解这些了,就会有思路了。

再说LruCache、DiskLruCache,可以看到,两者都有Lru,那么Lru是什么呢?这是目前常用的一种缓存算法:近期最少使用算法,核心思想很简单,就是当缓存满时,会优先删除那些近期最少使用的缓存。那么现在分别了解下这两种缓存吧。

LruCache

LruCache内部用到的是LinkedHashMap,LinkedHashMap与HashMap的不同住处在于LinkedHashMap 维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。也就说它的插入和访问是有顺序的。另外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 value) { return value.getRowBytes() * value.getHeight() / 1024;
        }
    };

总缓存大小一般会设置为当前进程可用内存的1/8,当然这个数是可以自己设置的,这个数是推荐的。sizeOf方法是为了计算缓存对象的大小。如果有必要也可以重写entryRemoved来完成某些资源回收工作。

再看缓存的添加与删除,

//添加缓存 mMemoryCache.put(key,bitmap); //获取缓存 mMemoryCache.get(key); //删除缓存 mMemoryCache.remove(key);

DiskLruCache

DiskLruCache用与磁盘缓存,被官方推荐使用。下面来看看它的使用。

自从用了Gradle后,引入项目方便多了,谁用谁知道。

compile 'com.jakewharton:disklrucache:2.0.2'

创建DiskLruCache:

DiskLruCache mDiskLruCache = null; try {  
    File cacheDir = getDiskCacheDir(context, "bitmap"); if (!cacheDir.exists()) {  
        cacheDir.mkdirs();  
    }  
    mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);  
} catch (IOException e) {  
    e.printStackTrace();  
}

解释下DiskLruCache.open的参数,第一个表示存储的路径,第二个表示应用的版本号,注意这里当版本号发生改变时会清空之前所有的缓存文件,而在实际开发中这个性质用的不多,所以直接写1。第三个表示单个节点对应的数据的个数,设置为1就可以了,第四个表示缓存的总大小,当超出这个值时,会清除一些缓存保证总大小不大于这个设定的值。

添加缓存:

第一步,网络下载图片(文件也是一样的步骤的)并通过outputStream写入到本地

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();  
    } try { if (out != null) {  
            out.close();  
        } if (in != null) {  
            in.close();  
        }  
    } catch (final IOException e) {  
        e.printStackTrace();  
    }  
} return false;  
}

第二步,处理缓存的key,直接用url作为key值时最有快捷的方式,但是url里会有特殊字符,不符合Android的命名规范,最好的办法就是把url进行MD5摘要。

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

第三步 创建DiskLruCache.Editor的实例,写入数据

String key = hashKeyForDisk(imageUrl);  
            DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) {  
                OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(imageUrl, outputStream)) {  
                    editor.commit();  
                } else {  
                    editor.abort();  
                }  
            }  
            mDiskLruCache.flush();

editor.commit()方法用来提交写入操作,editor.abort()回退整个操作。

读取缓存:

Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(0);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight); if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

需要说明下的是为了避免加载图片时导致OOM,不建议直接加在Bitmap,通常我们会通过BitmapFactory.Options来加载一张缩放的图片,但是这中方法对于FileInputStream有问题,因为FileInputStream是有序的文件流,而两次的从的 decodeStream调用影响了文件流的位置属性,导致第二次decodeStream时得到的为null。为了解决这个问题,可以先得到对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor()来加载图片。

移除缓存:

mDiskLruCache.remove(key);

 

来自:http://www.jianshu.com/p/96a7865fdab4

编程技巧