阿里云服务器免费领卷啦。

捡代码论坛-最全的游戏源码下载技术网站!

 找回密码
 立 即 注 册

QQ登录

只需一步,快速开始

搜索
关于源码区的附件失效或欺骗帖, 处理办法
查看: 2044|回复: 0

Android 加载大量图片的三级缓存处理

[复制链接]

4208

主题

210

回帖

12万

积分

管理员

管理员

Rank: 9Rank: 9Rank: 9

积分
126084
QQ
发表于 2017-2-13 15:26:24 | 显示全部楼层 |阅读模式
Android 加载大量图片的三级缓存处理。

在做新闻客户端的时候,有大量网络图片装载在ImageView显示,发现加载图片的时候
经常会出现OOM异常,这时候我上网查了不少资料,发现,其实图片加载的时候没必要
每次都从网络拉去,这时候就要用到缓存机制。经过查资料发现,图片缓存基本分为三级缓存:

  • 网络缓存
  • 内存缓存
  • 本地缓存

经过网上查询大量资料得出一些心得,下面一一详细说明。

网络缓存:

其实我觉得网络拉区图片也不算缓存,但是既然江湖规矩就是这样,我也把它称为网络
缓存算了,网络缓存实质是从网络拉去图片显示在ImageView中,下面代码中我写了一个
NetCacheUtil类作为网络缓存,我在其中用了AsyncTask 异步任务来实现下载功能。代码比较简单:

  1. public class NetCacheUtil {
  2.     private LocalCacheUtil mLocalCacheUtil;

  3.     public NetCacheUtil(LocalCacheUtil mLocalCacheUtil) {
  4.         this.mLocalCacheUtil = mLocalCacheUtil;
  5.     }

  6.     /**
  7.      * 从网络获取图片并显示在ImageView中
  8.      *
  9.      * @param ivPic
  10.      *            要显示图片的ImageView
  11.      * @param url
  12.      *            图片URL
  13.      */
  14.     public void getBitmapFromNet(ImageView ivPic, String url) {
  15.         new BitmapTask().execute(ivPic, url); // 启动
  16.     }

  17.     class BitmapTask extends AsyncTask<Object, Void, Bitmap> {

  18.         private ImageView ivPic;
  19.         private String url;

  20.         @Override
  21.         protected Bitmap doInBackground(Object... params) {
  22.             ivPic = (ImageView) params[0];
  23.             url = (String) params[1];
  24.             ivPic.setTag(url);
  25.             return downloadBitmap(url);
  26.         }

  27.         @Override
  28.         protected void onProgressUpdate(Void... values) {
  29.             super.onProgressUpdate(values);
  30.         }

  31.         @Override
  32.         protected void onPostExecute(Bitmap result) {
  33.             if (result != null) {
  34.                 if (ivPic.getTag().equals(url))

  35.                     //把Bitmap添加到本地缓存
  36.                     mLocalCacheUtil.setBitmapToLocal(url, result);
  37.                     ivPic.setImageBitmap(result);
  38.             }
  39.         }
  40.     }
复制代码
本地缓存

关于本地缓存我同样写了一个LocalCache来维护,里面使用了SD卡或者ROM内存保存了Bitmap位图,当加载ImageView 的时候优先使用本地缓存:

  1. /**
  2. * 本地缓存
  3. *
  4. * @author Administrator
  5. */
  6. public class LocalCacheUtil {
  7.     // 路径
  8.     public static final String CACHE_PATH = Environment
  9.             .getExternalStorageDirectory() + "/packageName/";
  10.     // 当sd卡不可用的时候使用rom内存
  11.     public static final File cacheFile = BaseApplication.getContext()
  12.             .getCacheDir();

  13.     /**
  14.      * 从本地读取数据
  15.      * @param url图片的保存地址
  16.      */
  17.     public Bitmap getBitmapFromLocal(String url) {
  18.         File file;
  19.         String fileName = md5(url);
  20.         // 当sdcard卡已经挂载的时候
  21.         if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
  22.             file = new File(CACHE_PATH, fileName);
  23.         } else {
  24.             file = cacheFile;
  25.         }
  26.         if (file.exists()) {
  27.             try {
  28.                 return BitmapFactory.decodeStream(new FileInputStream(file));
  29.             } catch (FileNotFoundException e) {
  30.                 e.printStackTrace();
  31.             }
  32.         }
  33.         return null;
  34.     }

  35.     /**
  36.      * 将文件名用md5 加密
  37.      * @param str
  38.      * @return
  39.      */
  40.     public String md5(String str) {
  41.         try {
  42.             MessageDigest instance = MessageDigest.getInstance("MD5");
  43.             byte[] digest = instance.digest(str.getBytes());
  44.             StringBuffer sb = new StringBuffer();

  45.             for (byte b : digest) {
  46.                 int i = b & 0xFF;
  47.                 String hex = Integer.toHexString(i);
  48.                 if (hex.length() < 2) {
  49.                     hex = "0" + hex;
  50.                 }
  51.                 sb.append(hex);
  52.             }
  53.             return sb.toString();
  54.         } catch (Exception e) {
  55.             e.printStackTrace();
  56.         }
  57.         return "";
  58.     }

  59.     /**
  60.      * 把图片保存到本地
  61.      *
  62.      * @param url 图片路径
  63.      * @param bitmap 位图对象
  64.      */
  65.     public void setBitmapToLocal(String url, Bitmap bitmap) {
  66.         File file;
  67.         String fileName = md5(url);
  68.         // 当sdcard卡已经挂载的时候
  69.         if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
  70.             file = new File(CACHE_PATH, fileName);
  71.         } else {
  72.             file = cacheFile;
  73.         }
  74.         try {
  75.             // 如果文件不存在就创建
  76.             if (!file.getParentFile().exists()) {
  77.                 file.getParentFile().mkdir();
  78.             }
  79.             // 把Bitmap对象以jpg格式保存
  80.             bitmap.compress(CompressFormat.JPEG, 100,
  81.                     new FileOutputStream(file));
  82.         } catch (Exception e) {
  83.             e.printStackTrace();
  84.         }

  85.     }
  86. }
复制代码
内存缓存

当然了,内存缓存是最重要的一块,同是也是最难处理的一块,内存缓存一个不小心就会导致OOM异常,开始的时候我是打算用一个HashMap来保存Bitmap对象,其中以图片URL作为key,Bitmap对象作为值。代码如下:

  1. /**
  2. * 内存缓存
  3. *
  4. * @author Administrator
  5. */
  6. public class MemoryCache {
  7.     // 维护Bitmap的集合
  8.     private HashMap<String, Bitmap> mMemoryCache = new HashMap<String, Bitmap>();

  9.     /**
  10.      * 从内存缓存中获取Bitmap
  11.      *
  12.      * @param url
  13.      *            图片URL
  14.      * @return
  15.      */
  16.     public Bitmap getBitmapFromMemoryCache(String url) {
  17.         Bitmap bitmap = mMemoryCache.get(url);
  18.         return bitmap == null ? null : bitmap;
  19.     }
  20.     /**
  21.      * 把Bitmap写进内存
  22.      * @param bitmap
  23.      * @param url
  24.      */
  25.     public void setBitmapToMemoryCache(Bitmap bitmap, String url) {
  26.         mMemoryCache.put(url, bitmap);
  27.     }
  28. }
复制代码

我在外部加载图片的代码是这样的:

  1. public class MyBitmapUtils {

  2.     NetCacheUtil mNetCacheUtil;
  3.     LocalCacheUtil mLocalCacheUtil;
  4.     MemoryCacheUtil mMemoryCacheUtil;

  5.     public MyBitmapUtils() {
  6.         mMemoryCacheUtil = new MemoryCacheUtil();
  7.         mLocalCacheUtil = new LocalCacheUtil();
  8.         mNetCacheUtil = new NetCacheUtil(mLocalCacheUtil);
  9.     }
  10.     /**
  11.      * 把url对应的图片显示在ImageView中
  12.      * @param ivPic
  13.      * @param url
  14.      */
  15.     public void display(ImageView ivPic, String url) {
  16.         ivPic.setImageResource(R.drawable.default);// 设置默认加载图片

  17.         Bitmap bitmap = null;
  18.         // 从内存读取图片
  19.         bitmap = mMemoryCacheUtil.getBitmapFromMemoryCache(url);
  20.         if (bitmap != null) {
  21.             ivPic.setImageBitmap(bitmap);
  22.             System.out.println("从内存读取图片啦...");
  23.             return;
  24.         }

  25.         // 从本地读取图片
  26.         bitmap = mLocalCacheUtil.getBitmapFromLocal(url);
  27.         if (bitmap != null) {
  28.             ivPic.setImageBitmap(bitmap);
  29.             System.out.println("从本地读取图片啦...");
  30.             //从本地读取图片成功后把图片添加到内存缓存中
  31.             mMemoryCacheUtil.setBitmapToMemoryCache(bitmap, url);
  32.             return;
  33.         }

  34.         // 从网络读图片
  35.         mNetCacheUtil.getBitmapFromNet(ivPic, url);
  36.     }

  37. }
复制代码

上面的类我做了一些处理,当我要加载一张图片的时候,我先从内存中获取,如果返回
值为空,我再从本地文件中读取,如果返回值也是null,这时候我才从网络拉取数据,
这样的3级缓存机制可以减少用户流量的使用。

当我写好上面3个工具类之后,满心欢喜去跑在模拟器上面,我滑啊滑啊,不同的页面来
回切,然后,程序还是崩了。

后来我仔细阅读了我的代码,发现我在内存缓存中维护的HashMap的size 一直在变大,
这说明了什么? 说明系统根本不会回收我HashMap的对象。后来又继续网上查资料,发现在Android 系统中默认给每个应用分配16M的内存,而Java语言中引用分为四种,下面我简单说一下特点:

  • 强引用: 一般引用,这是使用最普遍的引用。如果一个对象具有强引用,垃圾回
    收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError
    错误,也不会靠随意回收具有强引用的对象来解决内存不足问题。
  • 软引用(SoftReference):如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
  • 弱引用(WeakReference):弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  • 虚引用(PhantomReference):如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

这样一看,好像只有软引用适合我这种情况了,然后我就改造程序,把存进HashMap中的Bitamp对象设置为软引用。改造后的MemoryCacheUtil代码如下:

  1. /**
  2. * 内存缓存
  3. *
  4. * @author Administrator
  5. */
  6. public class MemoryCacheUtil {
  7.     // 维护Bitmap的集合
  8.     private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<String, SoftReference<Bitmap>>();

  9.     /**
  10.      * 从内存缓存中获取Bitmap
  11.      *
  12.      * @param url
  13.      *            图片URL
  14.      * @return
  15.      */
  16.     public Bitmap getBitmapFromMemoryCache(String url) {
  17.         SoftReference<Bitmap> softReference = mMemoryCache.get(url);
  18.         if (softReference != null) {
  19.             Bitmap bitmap = softReference.get();
  20.             return bitmap;
  21.         }
  22.         return null;
  23.     }

  24.     /**
  25.      * 把Bitmap写进内存
  26.      *
  27.      * @param bitmap
  28.      * @param url
  29.      */
  30.     public void setBitmapToMemoryCache(Bitmap bitmap, String url) {
  31.         SoftReference<Bitmap> b = new SoftReference<Bitmap>(bitmap);
  32.         mMemoryCache.put(url, b);
  33.     }
  34. }
复制代码

嗯,代码改造完毕后,我在模拟器上面滑来滑去,然后看后台输出,显示从内存读取的
情况寥寥无几,大部分都是从本地读取的。从结果看来,我猜应该是Bitmap对象存进去
的时候还没来得及复用就被系统回收了,这尼玛坑啊,搞了半天白干了。

后来又上网找资料,发现了一个牛逼的东西。在Android 3.0以上版本提供了一个特殊的类叫做LruCache,是Android Support V4 包里面提供的。然后代码再次改造。结果如下:

  1. /**
  2. * 内存缓存
  3. *
  4. * @author Administrator
  5. */
  6. public class MemoryCacheUtil {
  7.     // 维护Bitmap的集合
  8.     private LruCache<String, Bitmap> mLruCache;

  9.     public MemoryCacheUtil() {
  10.         long maxMemory = Runtime.getRuntime().maxMemory() / 8;// 模拟器默认是16M
  11.         mLruCache = new LruCache<String, Bitmap>((int) maxMemory) {
  12.             @Override
  13.             protected int sizeOf(String key, Bitmap value) {
  14.                 int byteCount = value.getRowBytes() * value.getHeight();
  15.                 return byteCount;
  16.             }
  17.         };
  18.     }

  19.     /**
  20.      * 从内存缓存中获取Bitmap
  21.      *
  22.      * @param url
  23.      *            图片URL
  24.      * @return
  25.      */
  26.     public Bitmap getBitmapFromMemoryCache(String url) {
  27.         Bitmap bitmap = mLruCache.get(url);
  28.         return bitmap == null ? null : bitmap;
  29.     }

  30.     /**
  31.      * 把Bitmap写进内存
  32.      *
  33.      * @param bitmap
  34.      * @param url
  35.      */
  36.     public void setBitmapToMemoryCache(Bitmap bitmap, String url) {
  37.         mLruCache.put(url, bitmap);
  38.     }
  39. }
复制代码

大家看代码也知道了,其实LruCache的用法和HashMap的用法差不多,只是需要在创建对象的时候传进去一个最大占用内存数,并且复写一个sizeOf方法,该方法返回一个对象所占用的字节数。
然后我比较好奇LruCache内部是怎么实现的,我就去翻一番它的源码,发现其实他的代
码就那么几百行。也是用一个LinkedHashMap实现的。其中它内部自己维护,当长度过大的时候就移除一个元素,核心代码如下:

  1. public void trimToSize(int maxSize) {
  2.         while (true) {
  3.             K key;
  4.             V value;
  5.             synchronized (this) {
  6.                 if (size < 0 || (map.isEmpty() && size != 0)) {
  7.                     throw new IllegalStateException(getClass().getName()
  8.                             + ".sizeOf() is reporting inconsistent results!");
  9.                 }

  10.                 if (size <= maxSize || map.isEmpty()) {
  11.                     break;
  12.                 }

  13.                 Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
  14.                 key = toEvict.getKey();
  15.                 value = toEvict.getValue();
  16.                 map.remove(key);
  17.                 size -= safeSizeOf(key, value);
  18.                 evictionCount++;
  19.             }

  20.             entryRemoved(true, key, value, null);
  21.         }
  22.     }
复制代码

好,终极版三级缓存处理新鲜出炉,然后试了下效果,在不断切换滑动的时候,读取的大部分都是内存缓存,程序也能坚持很久。但是最后也是会蹦。这个时候真的心累了,没辙了啊。

继续查查查,查到图片压缩这个功能,在从网络读取数据的时候把输入流构造成Bitmap的时候可以传进去一个参数:BitmapFactor.Options
该参数可以设置压缩比例,同时还可以设置图片格式等等参数,通过这些属性设置来让内存负荷减少是一种有效的方法,当然了,这些参数要设置得合理,不然显示不出图片效果。

  • option.inSimpleSize = 2 ; //压缩为二分之一,该值要根据图片要展示的大小来确定
  • option.inPreferredConfig = Bitmap.Config.RGB_565;设置图片格式

这个缓存机制有点坑爹啊,其实做了这么多处理,经过测试,还是有可能程序会崩掉的。

http://blog.csdn.net/u012943767/article/details/48186733


捡代码论坛-最全的游戏源码下载技术网站! - 论坛版权郑重声明:
1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者与捡代码论坛-最全的游戏源码下载技术网站!享有帖子相关版权
3、捡代码论坛版权,详细了解请点击。
4、本站所有内容均由互联网收集整理、网友上传,并且以计算机技术研究交流为目的,仅供大家参考、学习,不存在任何商业目的与商业用途。
5、若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。 我们不承担任何技术及版权问题,且不对任何资源负法律责任。
6、如无法链接失效或侵犯版权,请给我们来信:jiandaima@foxmail.com

回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立 即 注 册

本版积分规则

技术支持
在线咨询
QQ咨询
3351529868

QQ|手机版|小黑屋|捡代码论坛-专业源码分享下载 ( 陕ICP备15015195号-1|网站地图

GMT+8, 2024-3-29 17:39

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表