Android之ListView异步加载网络图片(优化缓存机制)

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:

1、采用线程池

2、内存缓存+文件缓存

3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4

4、对下载的图片进行按比例缩放,以减少内存的消耗

具体的代码里面说明。先放上内存缓存类的代码MemoryCache.java:

[java] view plain copy

  1. public class MemoryCache {
  2. private static final String TAG = "MemoryCache";
  3. // 放入缓存时是个同步操作
  4. // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
  5. // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
  6. private Map<String, Bitmap> cache = Collections
  7. .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
  8. // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
  9. private long size = 0;// current allocated size
  10. // 缓存只能占用的最大堆内存
  11. private long limit = 1000000;// max memory in bytes
  12. public MemoryCache() {
  13. // use 25% of available heap size
  14. setLimit(Runtime.getRuntime().maxMemory() / 4);
  15. }
  16. public void setLimit(long new_limit) {
  17. limit = new_limit;
  18. Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
  19. }
  20. public Bitmap get(String id) {
  21. try {
  22. if (!cache.containsKey(id))
  23. return null;
  24. return cache.get(id);
  25. } catch (NullPointerException ex) {
  26. return null;
  27. }
  28. }
  29. public void put(String id, Bitmap bitmap) {
  30. try {
  31. if (cache.containsKey(id))
  32. size -= getSizeInBytes(cache.get(id));
  33. cache.put(id, bitmap);
  34. size += getSizeInBytes(bitmap);
  35. checkSize();
  36. } catch (Throwable th) {
  37. th.printStackTrace();
  38. }
  39. }
  40. /**
  41. * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
  42. *
  43. */
  44. private void checkSize() {
  45. Log.i(TAG, "cache size=" + size + " length=" + cache.size());
  46. if (size > limit) {
  47. // 先遍历最近最少使用的元素
  48. Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
  49. while (iter.hasNext()) {
  50. Entry<String, Bitmap> entry = iter.next();
  51. size -= getSizeInBytes(entry.getValue());
  52. iter.remove();
  53. if (size <= limit)
  54. break;
  55. }
  56. Log.i(TAG, "Clean cache. New size " + cache.size());
  57. }
  58. }
  59. public void clear() {
  60. cache.clear();
  61. }
  62. /**
  63. * 图片占用的内存
  64. *
  65. * @param bitmap
  66. * @return
  67. */
  68. long getSizeInBytes(Bitmap bitmap) {
  69. if (bitmap == null)
  70. return 0;
  71. return bitmap.getRowBytes() * bitmap.getHeight();
  72. }
  73. }

也可以使用SoftReference,代码会简单很多,但是我推荐上面的方法。

[java] view plain copy

  1. public class MemoryCache {
  2. private Map<String, SoftReference<Bitmap>> cache = Collections
  3. .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
  4. public Bitmap get(String id) {
  5. if (!cache.containsKey(id))
  6. return null;
  7. SoftReference<Bitmap> ref = cache.get(id);
  8. return ref.get();
  9. }
  10. public void put(String id, Bitmap bitmap) {
  11. cache.put(id, new SoftReference<Bitmap>(bitmap));
  12. }
  13. public void clear() {
  14. cache.clear();
  15. }
  16. }

下面是文件缓存类的代码FileCache.java:

[java] view plain copy

  1. public class FileCache {
  2. private File cacheDir;
  3. public FileCache(Context context) {
  4. // 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
  5. // 没有SD卡就放在系统的缓存目录中
  6. if (android.os.Environment.getExternalStorageState().equals(
  7. android.os.Environment.MEDIA_MOUNTED))
  8. cacheDir = new File(
  9. android.os.Environment.getExternalStorageDirectory(),
  10. "LazyList");
  11. else
  12. cacheDir = context.getCacheDir();
  13. if (!cacheDir.exists())
  14. cacheDir.mkdirs();
  15. }
  16. public File getFile(String url) {
  17. // 将url的hashCode作为缓存的文件名
  18. String filename = String.valueOf(url.hashCode());
  19. // Another possible solution
  20. // String filename = URLEncoder.encode(url);
  21. File f = new File(cacheDir, filename);
  22. return f;
  23. }
  24. public void clear() {
  25. File[] files = cacheDir.listFiles();
  26. if (files == null)
  27. return;
  28. for (File f : files)
  29. f.delete();
  30. }
  31. }

最后最重要的加载图片的类,ImageLoader.java:

[java] view plain copy

  1. public class ImageLoader {
  2. MemoryCache memoryCache = new MemoryCache();
  3. FileCache fileCache;
  4. private Map<ImageView, String> imageViews = Collections
  5. .synchronizedMap(new WeakHashMap<ImageView, String>());
  6. // 线程池
  7. ExecutorService executorService;
  8. public ImageLoader(Context context) {
  9. fileCache = new FileCache(context);
  10. executorService = Executors.newFixedThreadPool(5);
  11. }
  12. // 当进入listview时默认的图片,可换成你自己的默认图片
  13. final int stub_id = R.drawable.stub;
  14. // 最主要的方法
  15. public void DisplayImage(String url, ImageView imageView) {
  16. imageViews.put(imageView, url);
  17. // 先从内存缓存中查找
  18. Bitmap bitmap = memoryCache.get(url);
  19. if (bitmap != null)
  20. imageView.setImageBitmap(bitmap);
  21. else {
  22. // 若没有的话则开启新线程加载图片
  23. queuePhoto(url, imageView);
  24. imageView.setImageResource(stub_id);
  25. }
  26. }
  27. private void queuePhoto(String url, ImageView imageView) {
  28. PhotoToLoad p = new PhotoToLoad(url, imageView);
  29. executorService.submit(new PhotosLoader(p));
  30. }
  31. private Bitmap getBitmap(String url) {
  32. File f = fileCache.getFile(url);
  33. // 先从文件缓存中查找是否有
  34. Bitmap b = decodeFile(f);
  35. if (b != null)
  36. return b;
  37. // 最后从指定的url中下载图片
  38. try {
  39. Bitmap bitmap = null;
  40. URL imageUrl = new URL(url);
  41. HttpURLConnection conn = (HttpURLConnection) imageUrl
  42. .openConnection();
  43. conn.setConnectTimeout(30000);
  44. conn.setReadTimeout(30000);
  45. conn.setInstanceFollowRedirects(true);
  46. InputStream is = conn.getInputStream();
  47. OutputStream os = new FileOutputStream(f);
  48. CopyStream(is, os);
  49. os.close();
  50. bitmap = decodeFile(f);
  51. return bitmap;
  52. } catch (Exception ex) {
  53. ex.printStackTrace();
  54. return null;
  55. }
  56. }
  57. // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
  58. private Bitmap decodeFile(File f) {
  59. try {
  60. // decode image size
  61. BitmapFactory.Options o = new BitmapFactory.Options();
  62. o.inJustDecodeBounds = true;
  63. BitmapFactory.decodeStream(new FileInputStream(f), null, o);
  64. // Find the correct scale value. It should be the power of 2.
  65. final int REQUIRED_SIZE = 70;
  66. int width_tmp = o.outWidth, height_tmp = o.outHeight;
  67. int scale = 1;
  68. while (true) {
  69. if (width_tmp / 2 < REQUIRED_SIZE
  70. || height_tmp / 2 < REQUIRED_SIZE)
  71. break;
  72. width_tmp /= 2;
  73. height_tmp /= 2;
  74. scale *= 2;
  75. }
  76. // decode with inSampleSize
  77. BitmapFactory.Options o2 = new BitmapFactory.Options();
  78. o2.inSampleSize = scale;
  79. return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
  80. } catch (FileNotFoundException e) {
  81. }
  82. return null;
  83. }
  84. // Task for the queue
  85. private class PhotoToLoad {
  86. public String url;
  87. public ImageView imageView;
  88. public PhotoToLoad(String u, ImageView i) {
  89. url = u;
  90. imageView = i;
  91. }
  92. }
  93. class PhotosLoader implements Runnable {
  94. PhotoToLoad photoToLoad;
  95. PhotosLoader(PhotoToLoad photoToLoad) {
  96. this.photoToLoad = photoToLoad;
  97. }
  98. @Override
  99. public void run() {
  100. if (imageViewReused(photoToLoad))
  101. return;
  102. Bitmap bmp = getBitmap(photoToLoad.url);
  103. memoryCache.put(photoToLoad.url, bmp);
  104. if (imageViewReused(photoToLoad))
  105. return;
  106. BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
  107. // 更新的操作放在UI线程中
  108. Activity a = (Activity) photoToLoad.imageView.getContext();
  109. a.runOnUiThread(bd);
  110. }
  111. }
  112. /**
  113. * 防止图片错位
  114. *
  115. * @param photoToLoad
  116. * @return
  117. */
  118. boolean imageViewReused(PhotoToLoad photoToLoad) {
  119. String tag = imageViews.get(photoToLoad.imageView);
  120. if (tag == null || !tag.equals(photoToLoad.url))
  121. return true;
  122. return false;
  123. }
  124. // 用于在UI线程中更新界面
  125. class BitmapDisplayer implements Runnable {
  126. Bitmap bitmap;
  127. PhotoToLoad photoToLoad;
  128. public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
  129. bitmap = b;
  130. photoToLoad = p;
  131. }
  132. public void run() {
  133. if (imageViewReused(photoToLoad))
  134. return;
  135. if (bitmap != null)
  136. photoToLoad.imageView.setImageBitmap(bitmap);
  137. else
  138. photoToLoad.imageView.setImageResource(stub_id);
  139. }
  140. }
  141. public void clearCache() {
  142. memoryCache.clear();
  143. fileCache.clear();
  144. }
  145. public static void CopyStream(InputStream is, OutputStream os) {
  146. final int buffer_size = 1024;
  147. try {
  148. byte[] bytes = new byte[buffer_size];
  149. for (;;) {
  150. int count = is.read(bytes, 0, buffer_size);
  151. if (count == -1)
  152. break;
  153. os.write(bytes, 0, count);
  154. }
  155. } catch (Exception ex) {
  156. }
  157. }
  158. }

主要流程是先从内存缓存中查找,若没有再开线程,从文件缓存中查找都没有则从指定的url中查找,并对bitmap进行处理,最后通过下面方法对UI进行更新操作。

[java] view plain copy

  1. a.runOnUiThread(...);

在你的程序中的基本用法:

[java] view plain copy

  1. ImageLoader imageLoader=new ImageLoader(context);
  2. ...
  3. imageLoader.DisplayImage(url, imageView);

比如你的放在你的ListView的adapter的getView()方法中,当然也适用于GridView。

OK,先到这。

时间: 2024-10-12 01:21:54

Android之ListView异步加载网络图片(优化缓存机制)的相关文章

Android之ListView异步加载网络图片(优化缓存机制)【转】

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决.下面提出一些优化: 1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: public class MemoryCache { private static final String TAG = "MemoryCa

(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加) 这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面. 比如: 一些只实现了异步加载,却没有线程池与软引用. 一些是用AsynTast的, 一些有了线程池但加载所有的图片,这样造成具大资源浪费 一些是用

Android之ListView异步加载图片且仅显示可见子项中的图片

折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整实例都没看到,只有自己一点点研究了,总体感觉 android 下面要显示个图片真不容易啊. 项目主要实现的功能: 异步加载图片图片内存缓存.异步磁盘文件缓存解决使用 viewHolder 后出现的图片错位问题优化列表滚动性能,仅显示可见子项中的图片无需固定图片显示高度,对高度进行缓存使列表滚动时不会

Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

Android中ListView异步加载图片错位.重复.闪烁问题分析及解决方案 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而ImageView控件就是View对象通

Android 异步加载网络图片并缓存到本地

在android应用开发的时候,加载网络图片是一个非常重要的部分,很多图片不可能放在本地,所以就必须要从服务器或者网络读取图片. 软引用是一个现在非常流行的方法,用户体验比较好,不用每次都需要从网络下载图片,如果下载后就存到本地,下次读取时首先查看本地有没有,如果没有再从网络读取. 记得2月份在和爱奇艺公司的项目总监一起搞联通的OTT盒子的时候他就提了一下软引用,奇艺做的手机客户端就是采用这种方法,所以你会发现奇艺客户端占用很大的空间,下面就分享一下异步加载网络图片的方法吧. FileCache

android实用技巧:android实现listview异步加载图片

针对listview异步加载图片这个问题,麦子学院android开发老师讲了一种非常实用的方法,麦子学院android开发老师说凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.

Android 实现ListView异步加载图片

ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.MalformedURLException; import java.net.UR

iOS开发swift版异步加载网络图片(带缓存和缺省图片)

iOS开发之swift版异步加载网络图片 与SDWebImage异步加载网络图片的功能相似,只是代码比较简单,功能没有SD的完善与强大,支持缺省添加图片,支持本地缓存. 异步加载图片的核心代码如下:  func setZYHWebImage(url:NSString?, defaultImage:NSString?, isCache:Bool){         var ZYHImage:UIImage?         if url == nil {             return   

Android中ListView异步加载数据

http://www.cnblogs.com/snake-hand/p/3206655.html 1.主Activity 1 public class MainActivity extends Activity { 2 3 private ListView listView; 4 private ArrayList<Person> persons; 5 private ListAdapter adapter; 6 private Handler handler=null; 7 //xml文件的