Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从网络上加载图片,应该将其缓存到内存和磁盘中,下次直接从内存或磁盘中获取,缓存策略一般使用 LRU(Least Recently Used) 算法,即最近最少使用算法,下面将从内存缓存和磁盘缓存两个方面以图片为例 介绍 Android 中如何使用缓存。
内存缓存
LruCache 是 Android 3.1 提供的一个缓存类,通过该类可以快速访问缓存的 Bitmap 对象,内部采用一个 LinkedHashMap 以强引用的方式存储需要缓存的 Bitmap 对象,当缓存超过指定的大小之前释放最近很少使用的对象所占用的内存。
注意:Android 3.1 之前,一个常用的内存缓存是一个 SoftReference 或 WeakReference 的位图缓存,现在已经不推荐使用了。Android 3.1 之后,垃圾回收器更加注重回收 SoftWeakference/WeakReference,这使得使用该种方式实现缓存很大程度上无效,使用 support-v4 兼容包中的 LruCache 可以兼容 Android 3.1 之前的版本。将从以下两个方面来学习,具体如下:
LruCache 的使用
加载网络图片
LruCache 的使用
初始化 LruCache
首先计算需要的缓存大小,具体如下:
1//第一种方式:2ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);3//获取当前硬件条件下应用所占的大致内存大小,单位为M4int memorySize = manager.getMemoryClass();//M5int cacheSize = memorySize/ 8;6//第二种方式(比较常用)7int memorySize = (int) Runtime.getRuntime().maxMemory();//bytes8int cacheSize = memorySize / 8;
然后,初始化 LruCache ,具体如下:
1//初始化 LruCache 且设置了缓存大小2LruCache lruCache = new LruCache(cacheSize){3 @Override4 protected int sizeOf(String key, Bitmap value) {5 //计算每一个缓存Bitmap的所占内存的大小,内存单位应该和 cacheSize 的单位保持一致6 return value.getByteCount();7 }8};
添加 Bitmap 对象到 LruCache 缓存中
1//参数put(String key,Bitmap bitmap)2lruCache.put(key,bitmap)
获取缓存中的图片并显示
1//参数get(String key)2Bitmap bitmap = lruCache.get(key);3imageView.setImageBitmap(bitmap);
下面使用 LruCache 加载一张网络图片来演示 LruCache 的简单使用。
加载网络图片
创建一个简单的 ImageLoader,里面封装获取缓存 Bitmap 、添加 Bitmap 到缓存中以及从缓存中移出 Bitmap 的方法,具体如下:
1//ImageLoader 2public class ImageLoader { 3 private LruCache lruCache; 4 public ImageLoader() { 5 int memorySize = (int) Runtime.getRuntime().maxMemory() / 1024; 6 7 int cacheSize = memorySize / 8; 8 lruCache = new LruCache(cacheSize){ 9 @Override10 protected int sizeOf(String key, Bitmap value) {11 //计算每一个缓存Bitmap的所占内存的大小12 return value.getByteCount()/1024;13 }14 };15 }1617 /**18 * 添加Bitmapd到LruCache中19 * @param key20 * @param bitmap21 */22 public void addBitmapToLruCache(String key, Bitmap bitmap){23 if (getBitmapFromLruCache(key)==null){24 lruCache.put(key,bitmap);25 }26 }2728 /**29 * 获取缓存的Bitmap30 * @param key31 */32 public Bitmap getBitmapFromLruCache(String key){33 if (key!=null){34 return lruCache.get(key);35 }36 return null;37 }3839 /**40 * 移出缓存41 * @param key42 */43 public void removeBitmapFromLruCache(String key){44 if (key!=null){45 lruCache.remove(key);46 }47 }48}
然后创建一个线程类用于加载图片,具体如下:
1//加载图片的线程 2public class LoadImageThread extends Thread { 3 private Activity mActivity; 4 private String mImageUrl; 5 private ImageLoader mImageLoader; 6 private ImageView mImageView; 7 8 public LoadImageThread(Activity activity,ImageLoader imageLoader, ImageView imageView,String imageUrl) { 9 this.mActivity = activity;10 this.mImageLoader = imageLoader;11 this.mImageView = imageView;12 this.mImageUrl = imageUrl;13 }1415 @Override16 public void run() {17 HttpURLConnection connection = null;18 InputStream is = null;19 try {20 URL url = new URL(mImageUrl);21 connection = (HttpURLConnection) url.openConnection();22 is = connection.getInputStream();23 if (connection.getResponseCode() == HttpURLConnection.HTTP_OK){24 final Bitmap bitmap = BitmapFactory.decodeStream(is);25 mImageLoader.addBitmapToLruCache("bitmap",bitmap);26 mActivity.runOnUiThread(new Runnable() {27 @Override28 public void run() {29 mImageView.setImageBitmap(bitmap);30 }31 });32 }33 } catch (IOException e) {34 e.printStackTrace();35 } finally {36 if (connection!=null){37 connection.disconnect();38 }39 if (is!=null){40 try {41 is.close();42 } catch (IOException e) {43 e.printStackTrace();44 }45 }46 }47 }48}
然后,在 MainActivity 中使用 ImageLoader 加载并缓存网络图片到内存中, 先从内存中获取,如果缓存中没有需要的 Bitmap ,则从网络上获取图片并添加到缓存中,使用过程中一旦退出应用,系统将会释放内存,关键方法如下:
1//获取图片 2private void loadImage(){ 3 Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap"); 4 if (bitmap==null){ 5 Log.i(TAG,"从网络获取图片"); 6 new LoadImageThread(this,imageLoader,imageView,url).start(); 7 }else{ 8 Log.i(TAG,"从缓存中获取图片"); 9 imageView.setImageBitmap(bitmap);10 }11}1213// 移出缓存14private void removeBitmapFromL(String key){15 imageLoader.removeBitmapFromLruCache(key);16}
然后在相应的事件里调用上述获取图片、移出缓存的方法,具体如下:
1@Override 2public void onClick(View v) { 3 switch (v.getId()){ 4 case R.id.btnLoadLruCache: 5 loadImage(); 6 break; 7 case R.id.btnRemoveBitmapL: 8 removeBitmapFromL("bitmap"); 9 break;10 }11}
下面来一张日志截图说明执行情况:
![a953257d931ded329d2bfa9a800b9732.png](https://i-blog.csdnimg.cn/blog_migrate/7277e83018066b2d972b28e3d36b90e4.jpeg)
jzman-blog
磁盘缓存
磁盘缓存就是指将缓存对象写入文件系统,使用磁盘缓存可有助于在内存缓存不可用时缩短加载时间,从磁盘缓存中获取图片相较从缓存中获取较慢,如果可以应该在后台线程中处理;磁盘缓存使用到一个 DiskLruCache 类来实现磁盘缓存,DiskLruCache 收到了 Google 官方的推荐使用,DiskLruCache 不属于 Android SDK 中的一部分,首先贴一个 DiskLruCache 的源码链接:
https://android.googlesource.com/platform/libcore/+/android-4.3_r3/luni/src/main/java/libcore/io/DiskLruCache.java,
主要内容如下:
DiskLruCache 的创建
DiskLruCache 缓存的添加
DiskLruCache 缓存的获取
DiskLruCache 的创建
DiskLruCache 的构造方法是私有的,故不能用来创建 DiskLruCache,它提供一个 open 方法用于创建自身,方法如下:
1/** 2 * 返回相应目录中的缓存,如果不存在则创建 3 * @param directory 缓存目录 4 * @param appVersion 表示应用的版本号,一般设为1 5 * @param valueCount 每个Key所对应的Value的数量,一般设为1 6 * @param maxSize 缓存大小 7 * @throws IOException if reading or writing the cache directory fails 8 */ 9public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)10 throws IOException {11 ...12 // 创建DiskLruCache13 DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);14 if (cache.journalFile.exists()) {15 ...16 return cache;17 }18 //如果缓存目录不存在,创建缓存目录以及DiskLruCache19 directory.mkdirs();20 cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);21 ...22 return cache;23}
注意:缓存目录可以选择 SD 卡上的缓存目录,及 /sdcard/Android/data/应用包名/cache 目录,也可以选择当前应用程序 data 下的缓存目录,当然可以指定其他目录,如果应用卸载后希望删除缓存文件,就选择 SD 卡上的缓存目录,如果希望保留数据请选择其他目录,还有一点,如果是内存缓存,退出应用之后缓存将会被清除。
DiskLruCache 缓存的添加
DiskLruCache 缓存的添加是通过 Editor 完成的,Editor 表示一个缓存对象的编辑对象,可以通过其 edit(String key) 方法来获取对应的 Editor 对象,如果 Editor 正在使用 edit(String key) 方法将会返回 null,即 DiskLruCache 不允许同时操作同一个缓存对象。当然缓存的添加都是通过唯一的 key 来进行添加操作的,那么什么作为 key 比较方便吗,以图片为例,一般讲 url 的 MD5 值作为 key ,计算方式如下:
1//计算url的MD5值作为key 2private String hashKeyForDisk(String url) { 3 String cacheKey; 4 try { 5 final MessageDigest mDigest = MessageDigest.getInstance("MD5"); 6 mDigest.update(url.getBytes()); 7 cacheKey = bytesToHexString(mDigest.digest()); 8 } catch (NoSuchAlgorithmException e) { 9 cacheKey = String.valueOf(url.hashCode());10 }11 return cacheKey;12}1314private String bytesToHexString(byte[] bytes) {15 StringBuilder sb = new StringBuilder();16 for (int i = 0; i 17 String hex = Integer.toHexString(0xFF & bytes[i]);18 if (hex.length() == 1) {19 sb.append('0');20 }21 sb.append(hex);22 }23 return sb.toString();24}
通过 url 的 MD5 的值获取到 key 之后,就可以通过 DiskLruCache 对象的 edit(String key) 方法获取 Editor 对象,然后通过 Editor 对象的 commit 方法,大概意思就是释放 Editir 对象,之后就可以通过 key 进行其他操作咯。
当然,获取到 key 之后就可以向 DiskLruCache 中添加要缓存的东西咯,要加载一个网络图片到缓存中,显然就是的通过下载的方式将要缓存的东西写入文件系统中,那么就需要一个输出流往里面写东西,主要有两种处理方式:
创建 OutputStream 写入要缓存的数据,通过 DiskLruCache 的 edit(String key) 方法获得 Editor 对象,然后通过 OutputStream 转换为 Birmap,将该 Bitmap 写入由 Editor 对象创建的 OutputStream 中,最后调用 Editor 对象的 commit 方法提交;
先获得 Editor 对象,根据 Editor 对象创建出 OutputStream 直接写入要缓存的数据,最后调用 Editor 对象的 commit 方法提交;
这里以第一种方式为例,将根据 url 将网络图片添加到磁盘缓存中,同时也添加到内存缓存中,具体如下:
1//添加网络图片到内存缓存和磁盘缓存 2public void putCache(final String url, final CallBack callBack){ 3 Log.i(TAG,"putCache..."); 4 new AsyncTask(){ 5 @Override 6 protected Bitmap doInBackground(String... params) { 7 String key = hashKeyForDisk(params[0]); 8 DiskLruCache.Editor editor = null; 9 Bitmap bitmap = null;10 try {11 URL url = new URL(params[0]);12 HttpURLConnection conn = (HttpURLConnection) url.openConnection();13 conn.setReadTimeout(1000 * 30);14 conn.setConnectTimeout(1000 * 30);15 ByteArrayOutputStream baos = null;16 if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){17 BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());18 baos = new ByteArrayOutputStream();19 byte[] bytes = new byte[1024];20 int len = -1;21 while((len=bis.read(bytes))!=-1){22 baos.write(bytes,0,len);23 }24 bis.close();25 baos.close();26 conn.disconnect();27 }28 if (baos!=null){29 bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),300,200);30 addBitmapToCache(params[0],bitmap);//添加到内存缓存31 editor = diskLruCache.edit(key);32 //关键33 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));34 editor.commit();//提交35 }36 } catch (IOException e) {37 try {38 editor.abort();//放弃写入39 } catch (IOException e1) {40 e1.printStackTrace();41 }42 }43 return bitmap;44 }4546 @Override47 protected void onPostExecute(Bitmap bitmap) {48 super.onPostExecute(bitmap);49 callBack.response(bitmap);50 }51 }.execute(url);52}
DiskLruCache 缓存的获取
在 DiskLruCache 缓存的添加中了解了如何获取 key,获取到 key 之后,通过 DiskLruCache 对象的 get 方法获得 Snapshot 对象,然后根据 Snapshot 对象获得 InputStream,最后通过 InputStream 就可以获得 Bitmap ,当然可以利用 上篇文章 中的对 Bitmap 采样的方式进行适当的调整,也可以在缓存之前先压缩再缓存,获取 InputStream 的方法具体如下:
1//获取磁盘缓存 2public InputStream getDiskCache(String url) { 3 Log.i(TAG,"getDiskCache..."); 4 String key = hashKeyForDisk(url); 5 try { 6 DiskLruCache.Snapshot snapshot = diskLruCache.get(key); 7 if (snapshot!=null){ 8 return snapshot.getInputStream(0); 9 }10 } catch (IOException e) {11 e.printStackTrace();12 }13 return null;14}
DiskLruCache 的主要部分大致如上,下面实现一个简单的三级缓存来说明 LruCache 和 DiskLruCache 的具体使用,MainActivity 代码如下:
1//MainActivity.java 2public class MainActivity extends AppCompatActivity { 3 private static final String TAG = "cache_test"; 4 public static String CACHE_DIR = "diskCache"; //缓存目录 5 public static int CACHE_SIZE = 1024 * 1024 * 10; //缓存大小 6 private ImageView imageView; 7 private LruCache lruCache; 8 private LruCacheUtils cacheUtils; 9 private String url = "http://img06.tooopen.com/images/20161012/tooopen_sy_181713275376.jpg";10 @Override11 protected void onCreate(Bundle savedInstanceState) {12 super.onCreate(savedInstanceState);13 setContentView(R.layout.activity_main);14 imageView = (ImageView) findViewById(R.id.imageView);15 }1617 @Override18 protected void onResume() {19 super.onResume();20 cacheUtils = LruCacheUtils.getInstance();21 //创建内存缓存和磁盘缓存22 cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE);23 }2425 @Override26 protected void onPause() {27 super.onPause();28 cacheUtils.flush();29 }3031 @Override32 protected void onStop() {33 super.onStop();34 cacheUtils.close();35 }3637 public void loadImage(View view){38 load(url,imageView);39 }4041 public void removeLruCache(View view){42 Log.i(TAG, "移出内存缓存...");43 cacheUtils.removeLruCache(url);44 }4546 public void removeDiskLruCache(View view){47 Log.i(TAG, "移出磁盘缓存...");48 cacheUtils.removeDiskLruCache(url);49 }5051 private void load(String url, final ImageView imageView){52 //从内存中获取图片53 Bitmap bitmap = cacheUtils.getBitmapFromCache(url);54 if (bitmap == null){55 //从磁盘中获取图片56 InputStream is = cacheUtils.getDiskCache(url);57 if (is == null){58 //从网络上获取图片59 cacheUtils.putCache(url, new LruCacheUtils.CallBack() {60 @Override61 public void response(Bitmap bitmap1) {62 Log.i(TAG, "从网络中获取图片...");63 Log.i(TAG, "正在从网络中下载图片...");64 imageView.setImageBitmap(bitmap1);65 Log.i(TAG, "从网络中获取图片成功...");66 }67 });68 }else{69 Log.i(TAG, "从磁盘中获取图片...");70 bitmap = BitmapFactory.decodeStream(is);71 imageView.setImageBitmap(bitmap);72 }73 }else{74 Log.i(TAG, "从内存中获取图片...");75 imageView.setImageBitmap(bitmap);76 }77 }78}
布局文件比较简单就不贴代码了,下面是日志运行截图说明执行情况,如下图所示:
![249da3183de63e1fe8c1158752fed11f.png](https://i-blog.csdnimg.cn/blog_migrate/73b3bdd3813b6ca73395c65b978273a9.jpeg)
jzman-blog
这篇文章记录了 LruCache 和 DiskLruCache 的基本使用方式,至少应该对这两个缓存辅助类有了一定的了解。
—————END—————