详解ListView加载网络图片的优化,让你轻松掌握!

写博客辛苦了,转载的朋友请标明出处哦,finddreams(http://blog.csdn.net/finddreams/article/details/40977451)

最近身边很多的人在问ListView加载网络图片该如何防止OOM,对于初学者来说ListView虽然平常用的比较多,但大多不知道该如何进行优化。同时,在面试的过程中ListView的优化问题也是最常会被问到的,以前面试中要是你能说出优化ListView的几个方法,那基本上面试官可能就会认可你的能力了。

我们来了解一些ListView在加载大量网络图片的时候存在的常见问题:

1.性能问题,ListView的滑动有卡顿,不流畅,造成非常糟糕的用户体验。

2.图片的错位问题。

3.图片太大,加载Bitmap时造成的OOM(Out of memory),也就是栈内存溢出。

4.异步线程丢失的问题。

针对所存在的问题我们逐个击破,彻底的掌握ListView的优化问题,有利于我们的学习和工作。

(一) 性能问题:

在这个问题上我们可以在Adapter适配器中中复用convertView 和写一个内部ViewHolder类来解决。但是如果每个Adapter中都写一个ViewHolder类会显得非常的麻烦,下面我给大家一个万能的ViewHolder类,方便在任何Adapter中调用。

  1. public class BaseViewHolder {
  2. @SuppressWarnings("unchecked")
  3. public static <T extends View> T get(View view, int id) {
  4. SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
  5. if (viewHolder == null) {
  6. viewHolder = new SparseArray<View>();
  7. view.setTag(viewHolder);
  8. }
  9. View childView = viewHolder.get(id);
  10. if (childView == null) {
  11. childView = view.findViewById(id);
  12. viewHolder.put(id, childView);
  13. }
  14. return (T) childView;
  15. }
  16. }

调用BaseViewHolder类的示例代码:

  1. if (convertView == null) {
  2. convertView = LayoutInflater.from(context).inflate(
  3. R.layout.personplans_item, parent, false);
  4. }
  5. TextView tv_product_type1 = BaseViewHolder.get(convertView,
  6. R.id.tv_product_type1);

注意:在BaseViewHolder类中我们看到SparseArray是Android提供的一个工具类 ,用意是用来取代HashMap工具类的。如下图:

SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高效率。具体如何提高效率可以去Android文档查询一下,这里就不赘述了。

(二)图片错位问题

这个问题导致的原因是因为复用ConvertView导致的,在加载大量的Item时,常见的错位问题。这种问题的解决思路通常是以图片的Url做为唯一的key,然后setTag中,然后获取时根据图片的URL来获得图片。

(三)防止OOM,以及异步加载。

关于异步加载图片的思路是:

1.第一次进入时,是没有图片的,这时候我们会启动一个线程池,异步的从网上获得图片数据,为了防止图片过大导致OOM,可以调用BitmapFactory中的Options类对图片进行适当的缩放,最后再显示主线程的ImageView上。

2.把加载好的图片以图片的Url做为唯一的key存入内存缓存当中,并严格的控制好这个缓存的大小,防止OOM的发生。

3.把图片缓存在SD当中,如果没有SD卡就放在系统的缓存目录cache中,以保证在APP退出后,下次进来能看到缓存中的图片,这样就可以让使你的APP不会给客户呈现一片空白的景象。

4.用户第二次进来的时候,加载图片的流程则是倒序的,首先从内容中看是否存在缓存图片,如果没有就从SD卡当中寻找,再没有然后才是从网络中获取图片数据。这样做的既可以提高加载图片的效率,同时也节约了用户的流量。

说完了理论性的东西,我们来开始动手实战一下吧,下面介绍一个GitHub上一个很轻巧的开源框架LazyListGitHub地址,然后基于它做一些我们想要的效果,关于开源的东西,我们不止要学会用,还要从中能学到东西。众所周知的Android-Universal-Image-Loader其实就是基于LazyList的一个拓展 ,增加了更多的配置。但是从学习的角度我们还是希望能从原理学起,太多的功能封装,难免会让我们晕乎,简单的功能实现就够了。

1.先来看一下运行效果图:

2.来看一下LazyList项目的结构:

构非常的简单,整个项目的体积才10多k,适合加入我们自己的项目当中去,研究起来也不会觉得难,因为都是最为核心的东西。

3.调用Lazylist的入口:

  1. adapter = new LazyAdapter(this, mStrings);
  2. list.setAdapter(adapter);

传入一个装满图片Url地址的字符串数组进去,然后在LazyAdapter中对ListView中的进行显示。

4.具体LazyAdapter中调用的代码是:

  1. public class LazyAdapter extends BaseAdapter {
  2. private Activity activity;
  3. private String[] data;
  4. private static LayoutInflater inflater=null;
  5. public ImageLoader imageLoader;
  6. public LazyAdapter(Activity a, String[] d) {
  7. activity = a;
  8. data=d;
  9. inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  10. imageLoader=new ImageLoader(activity.getApplicationContext());
  11. }
  12. public int getCount() {
  13. return data.length;
  14. }
  15. public Object getItem(int position) {
  16. return position;
  17. }
  18. public long getItemId(int position) {
  19. return position;
  20. }
  21. public View getView(int position, View convertView, ViewGroup parent) {
  22. View vi=convertView;
  23. if(convertView==null)
  24. vi = inflater.inflate(R.layout.item, null);
  25. ImageView image=BaseViewHolder.get(vi, R.id.image);
  26. ImageView image2=BaseViewHolder.get(vi, R.id.image2);
  27. imageLoader.DisplayImage(data[position], image);
  28. imageLoader.DisplayImage(data[position], image2);
  29. return vi;
  30. }
  31. }

5.从上面我们可以看出来其实最重要的封装显示图片的方法就在ImageLoader这个类中。

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

由于篇幅的原因,FileCache和MenoryCache这两个缓存类,我就不贴大量代码了,全部都在我提供的demo示例代码中,你可以仔细的去研究一下,并有详细的注释,大家下载就可以用在自己的项目中了。

Listview优化的demo地址:http://download.csdn.net/detail/finddreams/8141689

详解ListView加载网络图片的优化,让你轻松掌握!的更多相关文章

  1. 详解ListView加载网络图片的优化

    我们来了解一些ListView在加载大量网络图片的时候存在的常见问题: 1.性能问题,ListView的滑动有卡顿,不流畅,造成非常糟糕的用户体验. 2.图片的错位问题. 3.图片太大,加载Bitma ...

  2. 一步一步实现listview加载的性能优化

    listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次: 0.最原始的加载 1.利用convertView 2.利用ViewHol ...

  3. listview加载网络图片

    ListView加载网络数据和图片 2013-09-25 00:08:10|  分类: 默认分类 |  标签:android  |举报|字号 订阅     如,从服务器端获得商品名称.价格.简介和图片 ...

  4. Google推荐——Glide使用详解(图片加载框架)

    零.前言 本文所使用的Glide版本为3.7.0 一.简介 Glide,一个被google所推荐的图片加载库,作者是bumptech.这个库被广泛运用在google的开源项目中,包括2014年的goo ...

  5. [android]完美的解决方案ListView加载网络图片反弹问题

    为什么 先说为什么有照片反弹. 使用convertView对ListView的每一个item优化,item的复用能够有效减少内存的占用.使ListView滑动更为流畅. 但会带来一个问题,当最顶部的i ...

  6. linux modprobe命令参数及用法详解--linux加载模块命令

    转:http://www.linuxso.com/command/modprobe.html modprobe(module probe) 功能说明:自动处理可载入模块. 语 法:modprobe [ ...

  7. 详解Class加载过程

    1.Class文件内容格式 2.一个class文件是被加载到内存的过程是怎样的? loading 把一个class文件装到内存里,class文件是一个二进制,一个个的字节 linking Verifi ...

  8. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

  9. [WP8.1UI控件编程]Windows Phone大数据量网络图片列表的异步加载和内存优化

    11.2.4 大数据量网络图片列表的异步加载和内存优化 虚拟化技术可以让Windows Phone上的大数据量列表不必担心会一次性加载所有的数据,保证了UI的流程性.对于虚拟化的技术,我们不仅仅只是依 ...

随机推荐

  1. 【EasyNetQ】- 自动订阅者

    从v0.7.1.30开始,EasyNetQ简单易用AutoSubscriber.你可以用它来轻松地扫描实现任何接口的类的特定组件IConsume<T>或IConsumeAsync<T ...

  2. Redis能做什么?不能做什么?

    https://blog.csdn.net/u014229282/article/details/81174202 最近阅读了<redis设计与实现>,这是一本比较枯燥的书,毕竟涉及到re ...

  3. Java IO 小结

    Java IO 的学习需要明白流设计的体系结构,这样才能在实际需要的时候,通过API文档查阅,快速实现功能.

  4. background 背景图铺满界面

    background <body background="/image/1.png" style=" background-repeat:no-repeat ; b ...

  5. bug 跟蚊子的相似之处

    bug 跟蚊子的相似之处: 1.不知道藏在哪里. 2.不知道有多少. 3.总是在你即将睡觉休息的时候出现. 2 A:最近在看<一拳超人>,觉得咱们程序猿跟埼玉老师有点像啊! B:哪里像了? ...

  6. [Leetcode] Linked list cycle ii 判断链表是否有环

    Given a linked list, return the node where the cycle begins. If there is no cycle, returnnull. Follo ...

  7. BZOJ4008 [HNOI2015]亚瑟王 【概率dp】

    题目链接 BZOJ4008 题解 要求所有牌造成伤害的期望,就是求每一张牌发动的概率\(g[i]\) 我们发现一张牌能否发动,还与其前面的牌是否发动有关 那我们设\(f[i][j]\)表示前\(i\) ...

  8. phaser常用API总结

    1. 游戏画布的尺寸 var width = game.width, height = game.height;   2. 中心点坐标 var game = new Phaser.Game(...); ...

  9. loj2540 「PKUWC 2018」随机算法

    pkusc 快到了--做点题涨涨 rp. 记 \(f(S,i)\) 表示 \(S\) 这个集合是决计不能选的(要么属于独立集,要么和独立集相连),或称已经考虑了的,\(i\) 表示此集合对应的最大独立 ...

  10. 你对javascript的原生操作或者工具了解多少呢?

    1.有一个长度未知的数组a,如果它的长度为0就把数字1添加到数组里面,否则按照先进先出的队列规则让第一个元素出队. 分析:这道题主要是考核了数组的队列方法和栈方法.另外,原题还有字数限制的,只有在字数 ...