博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
详解ListView加载网络图片的优化,让你轻松掌握!
阅读量:5290 次
发布时间:2019-06-14

本文共 7485 字,大约阅读时间需要 24 分钟。

 写博客辛苦了,转载的朋友请标明出处哦,finddreams()

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

 

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

 

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

       2.图片的错位问题。

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

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

 

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

 

      (一) 性能问题:

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

 

[java] 
 
 
  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.   
  17. }  

        调用BaseViewHolder类的示例代码:

[java] 
 
 
  1. if (convertView == null) {  
  2.         convertView = LayoutInflater.from(context).inflate(  
  3.                 R.layout.personplans_item, parent, false);  
  4.     }  
  5.   
  6.   
  7.     TextView tv_product_type1 = BaseViewHolder.get(convertView,  
  8.             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上一个很轻巧的开源框架LazyList,然后基于它做一些我们想要的效果,关于开源的东西,我们不止要学会用,还要从中能学到东西。众所周知的Android-Universal-Image-Loader其实就是基于LazyList的一个拓展 ,增加了更多的配置。但是从学习的角度我们还是希望能从原理学起,太多的功能封装,难免会让我们晕乎,简单的功能实现就够了。

     

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

 

      

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

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

     

       3.调用Lazylist的入口:

 

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

  

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

      

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

    

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

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

      

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

 

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

      

      Listview优化的demo地址:

 

转载于:https://www.cnblogs.com/tc310/p/5345191.html

你可能感兴趣的文章
http://www.bootcss.com/
查看>>
20145308 《网络对抗》 注入shellcode+Return-to-libc攻击 学习总结
查看>>
将多张图片和文字合成一张图片
查看>>
自己动手写ORM(01):解析表达式树生成Sql碎片
查看>>
如何使用USBWebserver在本机快速建立网站测试环境
查看>>
百度Ueditor编辑器的Html模式自动替换样式的解决方法
查看>>
变量提升
查看>>
线性表可用顺序表或链表存储的优缺点
查看>>
在现有的mysql主从基础上,搭建mycat实现数据的读写分离
查看>>
opencv安装配置
查看>>
JAVA-初步认识-第六章-面向对象(举例)
查看>>
js合并数组
查看>>
cNoteSetCursor_命令窗口光标设置
查看>>
[Flex] flex手机项目如何限制横竖屏?只允许横屏?
查看>>
tensorflow的graph和session
查看>>
Benelux Algorithm Programming Contest 2014 Final(第二场)
查看>>
随机变量的期望为什么把不是自己密度函数当成自己的权重来求期望呢?
查看>>
6-1 并行程序模拟 uva210
查看>>
JavaScript动画打开半透明提示层
查看>>
Mybatis生成resulteMap时的注意事项
查看>>