Android工具类之图片加载第三方封装缓存优化

bb305977 7年前
   <h3>一、OOM的原因</h3>    <p>OOM:所谓的OOM指的就是Out-of-Memory内存不足啦。Android上加载图片OOM无非也就那么几个。</p>    <p>1、Bitmap用完没有回收,导致内存泄漏。2、手机像素越来越高,照片体积越来越大,在上传及加载时如不进行压缩处理,OOM是常有的事。3、机型偏旧、内存偏小。近几年Android的发力生猛,机型配置虽然一路飙升,但是仍然有一部分人还在用着两年前的机器。作为app的厂商又不能放弃这一部分用户。无奈,开发时还是得根据机型做适配。</p>    <h3>二、解决方案</h3>    <p>首选当然还是得选择第三方图片加载库,主流的加载库无非是UniversalImageLoader、Fresco、Glide、Picasso,推荐Glide。</p>    <p>按照界面图片尺寸,加载不同尺寸的图片</p>    <p>LruCache 缓存工具类</p>    <p>要实现图片加载最优,单单靠以上某一种方式处理肯定是不靠谱的,所以我们要使用的是三者结合来处理。是的,你没有听错。</p>    <h3>三、分析原由</h3>    <p>1.市面上主流的图片加载开源库,在磁盘缓存,内存管理,图片加载优化方面已经做了很好的处理,犯不着自己去实现一套图片加载机制,选择第三方开源库也是理所当然。一般情况,直接用第三方库加载图片即可,几乎不用做额外处理,当然复杂的情况就需要结合第三方库进行优化处理了。</p>    <p>2.按照不同的图片控件尺寸去加载图片,可以减少内存开销,节省资源,提高加载速度。例如微信朋友圈,在列表界面加载缩略小图,点击查看时才加载大图。我们项目开发时,图片上传与存储用的是七牛云存储,而七牛云存储本身提供强大的图片处理API,可以根据请求的链接,获取不同尺寸的图片,方便开发们结合自身项目需求,实现最优图片尺寸加载方案。七牛图片处理API文档地址放在文章最底下,有兴趣的可以了解下。</p>    <p>3.今天的主角LruCache,什么是LruCache?LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除。其在API12被引进,低版本可以用support包中的类。所以我们用LruCache来缓存加载的Bitmap,当内存低于我们设定的值后,LruCache便会自动帮我们回收用不到的资源。</p>    <h3>四、代码</h3>    <p>具体原因已经分析,废话不多说,直接上代码</p>    <p>1、LruCacheUtils工具类</p>    <pre>  <code class="language-java">import android.graphics.Bitmap;  import android.util.LruCache;    /**   * Created by leo on 16/8/17.   * LruCache 图片缓存优化处理类   */  public class LruCacheUtils extends LruCache<String, Bitmap> {      //获取手机内存大小      private static int MAXMEMONRY = (int) (Runtime.getRuntime().maxMemory() / 1024);      private static LruCacheUtils cacheUtils;        private LruCacheUtils(int maxSize) {          super(maxSize);      }        /**       * 单例       */      public static LruCacheUtils getInstance() {          if (cacheUtils == null) {              //创建对象时分配缓存,我们取内存的5分之一              cacheUtils = new LruCacheUtils(MAXMEMONRY / 5);          }          return cacheUtils;      }        @Override      protected int sizeOf(String key, Bitmap value) {          return value.getRowBytes() * value.getHeight() / 1024;      }        @Override      protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {          super.entryRemoved(evicted, key, oldValue, newValue);      }        /**       * 清理缓存       */      public void clearCache() {          if (cacheUtils.size() > 0) {              cacheUtils.evictAll();          }      }          /**       * 添加缓存图片       */      public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {          if (cacheUtils.get(key) != null) {              return;          }          if (!isEmpty(key) && bitmap != null) {              cacheUtils.put(key, bitmap);          }      }          /**       * 获取缓存图片       */      public synchronized Bitmap getBitmapFromMemCache(String key) {          if (isEmpty(key)) {              return null;          }          Bitmap bm = cacheUtils.get(key);          if (bm != null && !bm.isRecycled()) {              return bm;          }          return null;      }        /**       * 移除缓存       *       * @param key       */      public synchronized void removeImageCache(String key) {          if (isEmpty(key)) {              return;          }          Bitmap bm = cacheUtils.remove(key);          if (bm != null && !bm.isRecycled()) {              bm.recycle();          }      }          /**       * 判断字符串是否为空       *       * @param str       * @return       */      public boolean isEmpty(String... str) {          if (str == null) {              return true;          }          for (String s : str) {              if (s == null || s.isEmpty() || s.trim().isEmpty()) {                  return true;              }          }          return false;      }      }</code></pre>    <p>2、LruCacheUtils使用</p>    <pre>  <code class="language-java">String url = http://i2.buimg.com/567571/d208d52913b997bb.jpg?imageView2/2/w/       200;       ImageView photoView = new ImageView();       //判断缓存中是否已经缓存过该图片,有则直接拿Bitmap,没有则直接调用Glide加载并缓存Bitmap       Bitmap bitmap = LruCacheUtils.getInstance().getBitmapFromMemCache(url);       if (bitmap != null) {              photoView.setImageBitmap(bitmap);       } else {              PhotoLoader.displayImageTarget(photoView, url, getTarget(photoView,               url, position));          }</code></pre>    <p>3、图片加载方法</p>    <pre>  <code class="language-java">/**       * 加载图片 Target       *       * @param imageView       * @param target       * @param url       */      public void displayImageTarget(final ImageView imageView, final String       url, BitmapImageViewTarget target) {          Glide.get(imageView.getContext()).with(imageView.getContext())                  .load(url)                  .asBitmap()//强制转换Bitmap                  .diskCacheStrategy(DiskCacheStrategy.NONE)                  .into(target);      }          /**       * 获取BitmapImageViewTarget       */      private BitmapImageViewTarget getTarget(ImageView imageView, final String url,       final int position) {          return new BitmapImageViewTarget(imageView) {              @Override              protected void setResource(Bitmap resource) {                  super.setResource(resource);                  //缓存Bitmap,以便于在没有用到时,自动回收                  LruCacheUtils.getInstance().addBitmapToMemoryCache(url,                               resource);              }          };      }</code></pre>    <h3>五、调试查看。</h3>    <p>优化完成后,运行程序,在Android studio中找到Monitors一栏,进行图片查看测试,就能清楚的看到内存变化以及释放的过程啦。优化之前是直接使用Glide进行加载图片,内存曾一路飙升到200M,并且很难释放。加了缩略图以及LruCache优化后,内存一直保持在40M-80M之间。测试结果,基本上没有重现过OOM的情况。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5c6fa3fbb75d8b23b8d85341b9c0b506.png"></p>    <p>PS:建议app中所有加载过的bitmap都直接扔到LruCacheUtils中进行缓存,在bitmap没有使用时,方便系统对齐回收。调用上面代码前,请记得集成Glide开源库哦。如果你使用以上方法进行图片加载优化,还是会出现OOM的话,那就说明......你可能要换手机了……</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/d8890404d87b</p>    <p> </p>