Android中的缓存

ChaBedard 8年前
   <p>为什么会用到缓存呢?主要是流量耗不起啊,国内的公共场所的WiFi的普及率不高,因此必须考虑流量的问题,说白了,就是用户体验啊,每次都网络请求,消耗资源不说,网速不好的情况下还会有网络延时,用户体验不好。</p>    <p>Android中的缓存,从方式上来说,一般有网络缓存,磁盘缓存即SD卡缓存,内存缓存。网络缓存需要服务端的配合,用于加快网络请求的响应速度。磁盘缓存一般用DiskLruCache,当然也可以用SqlLite数据库,以及sharedpreference等作持久化处理。这里主要说下两种常用的缓存方法,LruCache、DiskLruCache。前者用于内存缓存,后者用于设备缓存,一般两者结合起来效果更好。</p>    <p>其实缓存的实现并不难,每一中缓存都会有三个基本操作,添加、获取、删除。了解这些了,就会有思路了。</p>    <p>再说LruCache、DiskLruCache,可以看到,两者都有Lru,那么Lru是什么呢?这是目前常用的一种缓存算法:近期最少使用算法,核心思想很简单,就是当缓存满时,会优先删除那些近期最少使用的缓存。那么现在分别了解下这两种缓存吧。</p>    <h3><strong>LruCache</strong></h3>    <p>LruCache内部用到的是LinkedHashMap,LinkedHashMap与HashMap的不同住处在于LinkedHashMap 维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。也就说它的插入和访问是有顺序的。另外LruCache是线程安全的。至于使用的话就很简单了。</p>    <pre>  <code class="language-java">// 初始化      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;          }      };</code></pre>    <p>总缓存大小一般会设置为当前进程可用内存的1/8,当然这个数是可以自己设置的,这个数是推荐的。sizeOf方法是为了计算缓存对象的大小。如果有必要也可以重写entryRemoved来完成某些资源回收工作。</p>    <p>再看缓存的添加与删除,</p>    <pre>  <code class="language-java">//添加缓存      mMemoryCache.put(key,bitmap);      //获取缓存      mMemoryCache.get(key);      //删除缓存      mMemoryCache.remove(key);</code></pre>    <h3><strong>DiskLruCache</strong></h3>    <p>DiskLruCache用与磁盘缓存,被官方推荐使用。下面来看看它的使用。</p>    <p>自从用了Gradle后,引入项目方便多了,谁用谁知道。</p>    <pre>  <code class="language-java">compile 'com.jakewharton:disklrucache:2.0.2'</code></pre>    <p>创建DiskLruCache:</p>    <pre>  <code class="language-java">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();    }</code></pre>    <p>解释下DiskLruCache.open的参数,第一个表示存储的路径,第二个表示应用的版本号,注意这里当版本号发生改变时会清空之前所有的缓存文件,而在实际开发中这个性质用的不多,所以直接写1。第三个表示单个节点对应的数据的个数,设置为1就可以了,第四个表示缓存的总大小,当超出这个值时,会清除一些缓存保证总大小不大于这个设定的值。</p>    <p>添加缓存:</p>    <p>第一步,网络下载图片(文件也是一样的步骤的)并通过outputStream写入到本地</p>    <pre>  <code class="language-java">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;    }</code></pre>    <p>第二步,处理缓存的key,直接用url作为key值时最有快捷的方式,但是url里会有特殊字符,不符合Android的命名规范,最好的办法就是把url进行MD5摘要。</p>    <pre>  <code class="language-java">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();    }</code></pre>    <p>第三步 创建DiskLruCache.Editor的实例,写入数据</p>    <pre>  <code class="language-java">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();</code></pre>    <p>editor.commit()方法用来提交写入操作,editor.abort()回退整个操作。</p>    <p>读取缓存:</p>    <pre>  <code class="language-java">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);              }          }</code></pre>    <pre>  <code class="language-java">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);      }</code></pre>    <p>需要说明下的是为了避免加载图片时导致OOM,不建议直接加在Bitmap,通常我们会通过BitmapFactory.Options来加载一张缩放的图片,但是这中方法对于FileInputStream有问题,因为FileInputStream是有序的文件流,而两次的从的 decodeStream调用影响了文件流的位置属性,导致第二次decodeStream时得到的为null。为了解决这个问题,可以先得到对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor()来加载图片。</p>    <p>移除缓存:</p>    <pre>  <code class="language-java">mDiskLruCache.remove(key);</code></pre>    <p> </p>    <p>来自:http://www.jianshu.com/p/96a7865fdab4</p>    <p> </p>