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>