Android图片缓存之Lru算法(二)

前言:上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小,点击查看。我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发生的概率呢?之前我们一直在使用SoftReference软引用,SoftReference是一种现在已经不再推荐使用的方式,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用变得不再可靠,所以今天我们来认识一种新的缓存处理算法Lru,然后学习一下基于Lru的Lrucache、DiskLruCache 实现我们的图片缓存。

Lru:LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓存10000条,那怎么确定删除哪条过期数据呢,采用LRU算法实现的话就是将最老的数据删掉。

基于LruCache实现内存缓存:1.)初始化MemoryCache这里内存缓存的是Drawable 而不是Bitmap 理由是Drawable相对Bitmap来说有很大的内存优势

 int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取系统分配给应用的总内存大小 int mCacheSize = maxMemory / 8;//设置图片内存缓存占用八分之一 mMemoryCache = new LruCache<String, Drawable>(mCacheSize) {  //必须重写此方法,来测量Bitmap的大小  @Override  protected int sizeOf(String key, Drawable value) {  if (value instanceof BitmapDrawable) {   Bitmap bitmap = ((BitmapDrawable) value).getBitmap();   return bitmap == null ? 0 : bitmap.getByteCount();  }  return super.sizeOf(key, value);  } };

2.)添加一个Drawable到内存缓存

 /** * 添加Drawable到内存缓存 * * @param key * @param drawable */ private void addDrawableToMemoryCache(String key, Drawable drawable) { if (getDrawableFromMemCache(key) == null && drawable != null) {  mMemoryCache.put(key, drawable); } }

3.)从内存缓存中获取一个Drawable

 /** * 从内存缓存中获取一个Drawable * * @param key * @return */ public Drawable getDrawableFromMemCache(String key) { return mMemoryCache.get(key); }

4.)从内存缓存中移除一个Drawable

 /** * 从内存缓存中移除 * * @param key */ public void removeCacheFromMemory(String key) { mMemoryCache.remove(key); }

5.)清空内存缓存

 /** * 清理内存缓存 */ public void cleanMemoryCCache() { mMemoryCache.evictAll(); } 

其实Lru缓存机制本质上就是存储在一个LinkedHashMap存储,为了保障插入的数据顺序,方便清理。

基于DiskLruCache实现磁盘缓存:DiskLruCache类并不是谷歌官方实现,需要自行下载,下载地址:https://github.com/JakeWharton/DiskLruCache

1.)初始化DiskLruCache

 File cacheDir = context.getCacheDir();//指定的是数据的缓存地址 long diskCacheSize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据 int appVersion = DiskLruUtils.getAppVersion(context);//指定当前应用程序的版本号 int valueCount = 1;//指定同一个key可以对应多少个缓存文件 try {  mDiskCache = DiskLruCache.open(cacheDir, appVersion, valueCount, diskCacheSize); } catch (Exception ex) { }

2.)写入一个文件到磁盘缓存

 /** * 添加Bitmap到磁盘缓存 * * @param key * @param value */ private void addBitmapToDiskCache(String key, byte[] value) { OutputStream out = null; try {  DiskLruCache.Editor editor = mDiskCache.edit(key);  if (editor != null) {  out = editor.newOutputStream(0);  if (value != null && value.length > 0) {   out.write(value);   out.flush();   editor.commit();  } else {   editor.abort();  }  }  mDiskCache.flush(); } catch (IOException e) {  e.printStackTrace(); } finally {  DiskLruUtils.closeQuietly(out); } }

3.)从磁盘缓存中读取Drawable

 /** * 从磁盘缓存中获取一个Drawable * * @param key * @return */ public Drawable getDrawableFromDiskCache(String key) { try {  DiskLruCache.Snapshot snapShot = mDiskCache.get(key);  if (snapShot != null) {  InputStream is = snapShot.getInputStream(0);  Bitmap bitmap = BitmapFactory.decodeStream(is);  Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);  //从磁盘中读取到之后 加入内存缓存  addDrawableToMemoryCache(key, drawable);  return drawable;  } } catch (IOException e) {  e.printStackTrace(); } return null; }

4.)从磁盘缓存中移除

 /** * 从磁盘缓存中移除 * * @param key */ public void removeCacheFromDisk(String key) { try {  mDiskCache.remove(key); } catch (Exception e) { } }

5.)清空磁盘缓存

 /** * 清理磁盘缓存 */ public void cleanDiskCache() { try {  mDiskCache.delete(); } catch (Exception e) { } }

图片下载过程:接下来实例中用到了一点RxJava的知识有不了解RxJava的请自行了解一下。1.)采用异步方式操作磁盘缓存和网络下载, 内存缓存可以在主线程中操作

 public void disPlay(final ImageView imageView, String imageUrl) {  //生成唯一key  final String key = DiskLruUtils.hashKeyForDisk(imageUrl);  //先从内存中读取  Drawable drawableFromMemCache = getDrawableFromMemCache(key);  if (drawableFromMemCache != null) {   imageView.setImageDrawable(drawableFromMemCache);   return;  }  Observable.just(imageUrl)    .map(new Func1<String, Drawable>() {     @Override     public Drawable call(String imageUrl) { // 参数类型 String      //从磁盘中读取      Drawable drawableFromDiskCache = getDrawableFromDiskCache(key);      if (drawableFromDiskCache != null) {       return drawableFromDiskCache;      }      //网络下载      return download(imageUrl); // 返回类型 Drawable     }    })    .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程    .subscribe(new Action1<Drawable>() {     @Override     public void call(Drawable drawable) { // 参数类型 Drawable      imageView.setImageDrawable(drawable);     }    }); }

2.)下载图片过程以及处理

 private Drawable download(String imageUrl) {  HttpURLConnection urlConnection = null;  ByteArrayOutputStream bos = null;  InputStream ins = null;  try {   final URL url = new URL(imageUrl);   urlConnection = (HttpURLConnection) url.openConnection();   ins = urlConnection.getInputStream();   bos = new ByteArrayOutputStream();   int b;   while ((b = ins.read()) != -1) {    bos.write(b);   }   bos.flush();   byte[] bytes = bos.toByteArray();   Bitmap bitmap = DiskLruUtils.bytes2Bitmap(bytes);   String key = DiskLruUtils.hashKeyForDisk(imageUrl);   Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);   //加入内存缓存   addDrawableToMemoryCache(key, drawable);   //加入磁盘缓存   addBitmapToDiskCache(key, bytes);   return drawable;  } catch (IOException e) {   e.printStackTrace();  } finally {   if (urlConnection != null) {    urlConnection.disconnect();   }   DiskLruUtils.closeQuietly(bos);   DiskLruUtils.closeQuietly(ins);  }  return null; }

附上最终图片缓存单例简单实现全部代码以及DiskLruUtils工具类代码ImageLoadManager.java

public class ImageLoadManager { private LruCache<String, Drawable> mMemoryCache;//内存缓存 private DiskLruCache mDiskCache;//磁盘缓存 private static ImageLoadManager mInstance;//获取图片下载单例引用 /**  * 构造器  *  * @param context  */ private ImageLoadManager(Context context) {  int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取系统分配给应用的总内存大小  int mCacheSize = maxMemory / 8;//设置图片内存缓存占用八分之一  mMemoryCache = new LruCache<String, Drawable>(mCacheSize) {   //必须重写此方法,来测量Bitmap的大小   @Override   protected int sizeOf(String key, Drawable value) {    if (value instanceof BitmapDrawable) {     Bitmap bitmap = ((BitmapDrawable) value).getBitmap();     return bitmap == null ? 0 : bitmap.getByteCount();    }    return super.sizeOf(key, value);   }  };  File cacheDir = context.getCacheDir();//指定的是数据的缓存地址  long diskCacheSize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据  int appVersion = DiskLruUtils.getAppVersion(context);//指定当前应用程序的版本号  int valueCount = 1;//指定同一个key可以对应多少个缓存文件  try {   mDiskCache = DiskLruCache.open(cacheDir, appVersion, valueCount, diskCacheSize);  } catch (Exception ex) {  } } /**  * 获取单例引用  *  * @return  */ public static ImageLoadManager getInstance(Context context) {  ImageLoadManager inst = mInstance;  if (inst == null) {   synchronized (RequestManager.class) {    inst = mInstance;    if (inst == null) {     inst = new ImageLoadManager(context.getApplicationContext());     mInstance = inst;    }   }  }  return inst; } public void disPlay(final ImageView imageView, String imageUrl) {  //生成唯一key  final String key = DiskLruUtils.hashKeyForDisk(imageUrl);  //先从内存中读取  Drawable drawableFromMemCache = getDrawableFromMemCache(key);  if (drawableFromMemCache != null) {   imageView.setImageDrawable(drawableFromMemCache);   return;  }  Observable.just(imageUrl)    .map(new Func1<String, Drawable>() {     @Override     public Drawable call(String imageUrl) { // 参数类型 String      //从磁盘中读取      Drawable drawableFromDiskCache = getDrawableFromDiskCache(key);      if (drawableFromDiskCache != null) {       return drawableFromDiskCache;      }      //网络下载      return download(imageUrl); // 返回类型 Drawable     }    })    .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程    .subscribe(new Action1<Drawable>() {     @Override     public void call(Drawable drawable) { // 参数类型 Drawable      imageView.setImageDrawable(drawable);     }    }); } /**  * 添加Drawable到内存缓存  *  * @param key  * @param drawable  */ private void addDrawableToMemoryCache(String key, Drawable drawable) {  if (getDrawableFromMemCache(key) == null && drawable != null) {   mMemoryCache.put(key, drawable);  } } /**  * 从内存缓存中获取一个Drawable  *  * @param key  * @return  */ public Drawable getDrawableFromMemCache(String key) {  return mMemoryCache.get(key); } /**  * 从磁盘缓存中获取一个Drawable  *  * @param key  * @return  */ public Drawable getDrawableFromDiskCache(String key) {  try {   DiskLruCache.Snapshot snapShot = mDiskCache.get(key);   if (snapShot != null) {    InputStream is = snapShot.getInputStream(0);    Bitmap bitmap = BitmapFactory.decodeStream(is);    Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);    //从磁盘中读取到之后 加入内存缓存    addDrawableToMemoryCache(key, drawable);    return drawable;   }  } catch (IOException e) {   e.printStackTrace();  }  return null; } /**  * 添加Bitmap到磁盘缓存  *  * @param key  * @param value  */ private void addBitmapToDiskCache(String key, byte[] value) {  OutputStream out = null;  try {   DiskLruCache.Editor editor = mDiskCache.edit(key);   if (editor != null) {    out = editor.newOutputStream(0);    if (value != null && value.length > 0) {     out.write(value);     out.flush();     editor.commit();    } else {     editor.abort();    }   }   mDiskCache.flush();  } catch (IOException e) {   e.printStackTrace();  } finally {   DiskLruUtils.closeQuietly(out);  } } private Drawable download(String imageUrl) {  HttpURLConnection urlConnection = null;  ByteArrayOutputStream bos = null;  InputStream ins = null;  try {   final URL url = new URL(imageUrl);   urlConnection = (HttpURLConnection) url.openConnection();   ins = urlConnection.getInputStream();   bos = new ByteArrayOutputStream();   int b;   while ((b = ins.read()) != -1) {    bos.write(b);   }   bos.flush();   byte[] bytes = bos.toByteArray();   Bitmap bitmap = DiskLruUtils.bytes2Bitmap(bytes);   String key = DiskLruUtils.hashKeyForDisk(imageUrl);   Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);   //加入内存缓存   // addDrawableToMemoryCache(key, drawable);   //加入磁盘缓存   addBitmapToDiskCache(key, bytes);   return drawable;  } catch (IOException e) {   e.printStackTrace();  } finally {   if (urlConnection != null) {    urlConnection.disconnect();   }   DiskLruUtils.closeQuietly(bos);   DiskLruUtils.closeQuietly(ins);  }  return null; } /**  * 从缓存中移除  *  * @param key  */ public void removeCache(String key) {  removeCacheFromMemory(key);  removeCacheFromDisk(key); } /**  * 从内存缓存中移除  *  * @param key  */ public void removeCacheFromMemory(String key) {  mMemoryCache.remove(key); } /**  * 从磁盘缓存中移除  *  * @param key  */ public void removeCacheFromDisk(String key) {  try {   mDiskCache.remove(key);  } catch (Exception e) {  } } /**  * 磁盘缓存大小  *  * @return  */ public long diskCacheSize() {  return mDiskCache.size(); } /**  * 内存缓存大小  *  * @return  */ public long memoryCacheSize() {  return mMemoryCache.size(); } /**  * 关闭磁盘缓存  */ public void closeDiskCache() {  try {   mDiskCache.close();  } catch (Exception e) {  } } /**  * 清理缓存  */ public void cleanCache() {  cleanMemoryCCache();  cleanDiskCache(); } /**  * 清理磁盘缓存  */ public void cleanDiskCache() {  try {   mDiskCache.delete();  } catch (Exception e) {  } } /**  * 清理内存缓存  */ public void cleanMemoryCCache() {  mMemoryCache.evictAll(); }}

DiskLruUtils.java

final class DiskLruUtils { /**  * 关闭输入输出流  */ public static void closeQuietly(/*Auto*/Closeable closeable) {  if (closeable != null) {   try {    closeable.close();   } catch (RuntimeException rethrown) {    throw rethrown;   } catch (Exception ignored) {   }  } } /**  * 获取versionCode  */ public static int getAppVersion(Context context) {  try {   PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);   return info.versionCode;  } catch (PackageManager.NameNotFoundException e) {   e.printStackTrace();  }  return 1; } public static 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; } public static 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(); } /**  * Bitmap → bytes  */ public static byte[] bitmap2Bytes(Bitmap bm) {  if (bm == null) {   return null;  }  ByteArrayOutputStream baos = new ByteArrayOutputStream();  bm.compress(Bitmap.CompressFormat.PNG, 100, baos);  return baos.toByteArray(); } /**  * bytes → Bitmap  */ public static Bitmap bytes2Bitmap(byte[] bytes) {  return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } /**  * Drawable → Bitmap  */ public static Bitmap drawable2Bitmap(Drawable drawable) {  if (drawable == null) {   return null;  }  // 取 drawable 的长宽  int w = drawable.getIntrinsicWidth();  int h = drawable.getIntrinsicHeight();  // 取 drawable 的颜色格式  Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;  // 建立对应 bitmap  Bitmap bitmap = Bitmap.createBitmap(w, h, config);  // 建立对应 bitmap 的画布  Canvas canvas = new Canvas(bitmap);  drawable.setBounds(0, 0, w, h);  // 把 drawable 内容画到画布中  drawable.draw(canvas);  return bitmap; } /*   * Bitmap → Drawable   */ public static Drawable bitmap2Drawable(Bitmap bm) {  if (bm == null) {   return null;  }  BitmapDrawable bd = new BitmapDrawable(bm);  bd.setTargetDensity(bm.getDensity());  return new BitmapDrawable(bm); }}

以上就是基于Lru图片缓存简单实现,希望对大家的学习有所帮助,也希望大家多多支持。

人生的失败往往是在关键时刻少了坚持。

Android图片缓存之Lru算法(二)

相关文章:

你感兴趣的文章:

标签云: