ListView终极优化方法,绝对流畅

来源:互联网 发布:手机下载监控软件 编辑:程序博客网 时间:2024/05/01 21:14
 

ListView终极优化方法,绝对流畅

分类: Android 4407人阅读 评论(12) 收藏 举报
ListView优化ViewHoldergetView系统联系人耗时操作
     listview可以说是Android开发中最常见的UI控件了,listview能够以列表的方式显示大量同类的数据,这样问题就产生了,既然是大量数据,就会使用到很多布局,给布局绑定数据,listview将占用大量资源还可能会产生卡顿现象。
     listview现在最常用也拥有很好的性能的优化方式是在Adapter中使用静态的ViewHolder,具体代码如下:
      Activity
     private TestAdapter mAdapter;

    private String[] mArrData;
    
private TextView mTV;

    @Override
    
protected void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTV 
= (TextView) findViewById(R.id.tvShow);

        mArrData 
= new String[1000];
        
for (int i = 0; i < 1000; i++) {
            mArrData[i] 
= "Google IO Adapter" + i;
        }
        mAdapter 
= new TestAdapter(this, mArrData);
        ((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
    }
         Adapter
      private int count = 0;
    private long sum = 0L;

        @Override
        
public View getView(int position, View convertView, ViewGroup parent) {
            
// 开始计时
            long startTime = System.nanoTime();

            ViewHolder holder;
            
if (convertView == null) {
                convertView 
= mInflater.inflate(R.layout.list_item_icon_text,
                        
null);
                holder 
= new ViewHolder();
                holder.icon1 
= (ImageView) convertView.findViewById(R.id.icon1);
                holder.text1 
= (TextView) convertView.findViewById(R.id.text1);
                holder.icon2 
= (ImageView) convertView.findViewById(R.id.icon2);
                holder.text2 
= (TextView) convertView.findViewById(R.id.text2);
                convertView.setTag(holder);
            }
            
else{
                holder 
= (ViewHolder)convertView.getTag();
            }
            holder.icon1.setImageResource(R.drawable.icon);
            holder.text1.setText(mData[position]);
            holder.icon2 .setImageResource(R.drawable.icon);
            holder.text2.setText(mData[position]);

            
// 停止计时
            long endTime = System.nanoTime();
            
// 计算耗时
            long val = (endTime - startTime) / 1000L;
            Log.e(
"Test""Position:" + position + ":" + val);
            
if (count < 100) {
                
if (val < 1000L) {
                    sum 
+= val;
                    count
++;
                }
            } 
else
                mTV.setText(String.valueOf(sum 
/ 100L));// 显示统计结果
            return convertView;
        }
    }

    
static class ViewHolder {
        TextView text1;
        ImageView icon1;
        TextView text2;
        ImageView icon2;
    }

       在Adapter的代码中,在getView方法里首先判断convertView是否为空,若为空则加载相应布局,若不为空则
直接使用该布局,这能够很有效的使用Android为listview提供的缓存机制:只加载一屏的布局,之后滑动出来的item使用的是之前已经加载的布局的缓存;
       而使用静态的ViewHoulder的目的则是节省了findViewById的时间。如果不使用ViewHolder,每次getView的时候都需要得到一次子布局,而这也是很耗时并且耗资源的;如果使用了ViewHolder作为子布局的缓存,使用View的setTag方法将缓存与每个item绑定,则也可以省去了findViewById的事件;而将ViewHolder设置为static的目的是指在初始化Adapter时初始化一次这个内部类,否则将会在每次创建Adapter时都要初始化一次,而这是没有必要的。

      上述方法能够解决大部分listview消耗资源以及卡顿的问题,但对于不同的需求的listview来说还会存在其他让listview卡顿的原因,比如listview的item每次加载时都需要获得图片并设置到imageview中,item加载时需要进行大量的计算,item里的TextView需要设置指定字体;这些耗时的操作都会让listview滑动起来很卡,带来不好的体验;
          这几天我一直在研究Android4.0+系统联系人的源码,因为我发现,系统联系人采用的也是listview布局,每一个item都有图片文字,而且使用了fastscroller模式,对联系人以首字母进行了分来,同时Adapter还实现了SectionIndexer接口,能够实现这种效果;但是却一点都不卡,而我自己做的一个类似的界面,却十分卡顿,很影响用户体验;于是我找来了Android体统联系人的源码,自己建立起来了一个可以运行的程序,然后去研究它是如何实现这么流畅的listview的;

       经过研究我发现系统联系人的listview使用的是自定义的listview:PinnedHeaderListView 它的定义是这样的:
/**
 * A ListView that maintains a header pinned at the top of the list. The
 * pinned header can be pushed up and dissolved as needed.
 */ 
       它给listview加了一个header即显示在联系人界面最上方贴着屏幕顶部的那部分,同时它继承自AutoScrollListView 而这个AutoScrollListView继承自listview,对listview进行了一些优化,让listview在互动到指定的item时更流畅;

       看到这里,于是我使用了这个AutoScrollListView ,但效果不是很明显,listview还是很卡;在系统联系人使用的listview中没有找到解决方法,我开始研究它的Adapter;研究Adapter发现,系统的Adapter进行了很多层的封装,完全淡化了geiView方法;使用了很多AsyncTask来加载不同的数据,然后使用了CursorLoader来将加载好的数据添加到Adapter里,同时还将图片的加载与数据的加载进行了分离,代码逻辑十分复杂;但通过这个我得出了一个结论:不要将任何的耗时操作放在listview的getView方法里。

       在系统联系人的这些Adapter里我发现getView方法中没有任何的耗时操作,在设置图片时图片已经得到,对列表按照字母进行的分类也已经分类好了,存放在一个内部类里;在得出这个结论之前我尝试过很多系统联系人中的代码,但都没有得到明显的效果,经过大量的测试我得出了这个结论,并且在测试中得到了验证。

        在我的项目中,listview的每一个item都有一个图片,和很多TextView,而且所有的TextView都要设置非系统的字体;Adapter使用的是ViewHolder优化,在getView中的代码已经很少了,但是还是卡;我的listview中的数据是一个对象的List,在对象里只存放了item需要展示的图片的资源ID,或者是图片的路径,需要通过一些操作才能获得图片,而这些操作其实是很耗时的;于是我将原来的对象进行了更改,将图片对象直接存放在item对应的对象中,然后再Adapter初始化的时候将这些对象初始化,虽然listview展示所需的时间稍微长了一点,但是结果是listview滑动流畅了很多;接着我又将从assets中获得字体TypeFace的操作放在了Adapter初始化的方法中,并且将字体通过静态的变量都存起来,然后再getView中只需为TextView设置一下taptface即可,不需要在从asset中获取字体所花费的时间;通过上面两步操作之后,我的listview一点都不卡了,十分流畅(将图片放在List的每个数据项里虽然能解决问题,但不是一个很好的解决方案,后来我使用了内存缓存加上异步加载图片的方式也保证了列表的流畅)。

        我这里提供我使用的内存缓存的一个实现:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class MemoryCache {  
  2.   
  3.     private static final String TAG = "MemoryCache";  
  4.     private Map<String, Bitmap> cache=Collections.synchronizedMap(  
  5.             new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering  
  6.     private long size=0;//current allocated size  
  7.     private long limit=1000000;//max memory in bytes  
  8.   
  9.     public MemoryCache(){  
  10.         //use 25% of available heap size  
  11.         setLimit(Runtime.getRuntime().maxMemory()/4);  
  12.     }  
  13.       
  14.     public void setLimit(long new_limit){  
  15.         limit=new_limit;  
  16.         Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");  
  17.     }  
  18.   
  19.     public Bitmap get(String id){  
  20.         try{  
  21.             if(!cache.containsKey(id))  
  22.                 return null;  
  23.             //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78   
  24.             return cache.get(id);  
  25.         }catch(NullPointerException ex){  
  26.             ex.printStackTrace();  
  27.             return null;  
  28.         }  
  29.     }  
  30.   
  31.     public void put(String id, Bitmap bitmap){  
  32.         try{  
  33.             if(cache.containsKey(id))  
  34.                 size-=getSizeInBytes(cache.get(id));  
  35.             cache.put(id, bitmap);  
  36.             size+=getSizeInBytes(bitmap);  
  37.             checkSize();  
  38.         }catch(Throwable th){  
  39.             th.printStackTrace();  
  40.         }  
  41.     }  
  42.       
  43.     private void checkSize() {  
  44.         Log.i(TAG, "cache size="+size+" length="+cache.size());  
  45.         if(size>limit){  
  46.             Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated    
  47.             while(iter.hasNext()){  
  48.                 Entry<String, Bitmap> entry=iter.next();  
  49.                 size-=getSizeInBytes(entry.getValue());  
  50.                 iter.remove();  
  51.                 if(size<=limit)  
  52.                     break;  
  53.             }  
  54.             Log.i(TAG, "Clean cache. New size "+cache.size());  
  55.         }  
  56.     }  
  57.   
  58.     public void clear() {  
  59.         try{  
  60.             //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78   
  61.             cache.clear();  
  62.             size=0;  
  63.         }catch(NullPointerException ex){  
  64.             ex.printStackTrace();  
  65.         }  
  66.     }  
  67.   
  68.     long getSizeInBytes(Bitmap bitmap) {  
  69.         if(bitmap==null)  
  70.             return 0;  
  71.         return bitmap.getRowBytes() * bitmap.getHeight();  
  72.     }  
  73. }  



        综上,listview的优化其实就是去找getView中的耗时操作,然后提取出来,要么使用异步的方式为item的布局设置数据,要是实在需要同步,就只能在Adapter初始化时将数据准备好,然后再getView中只需绑定一下就行。
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 通过中介买房产生纠纷怎么办 天津公租房资格证到期怎么办 买大房子后悔了怎么办 公款私存了该怎么办 外地人怎么办身份证在北京东城区 申请公租房有车怎么办 租了公租房辞职怎么办 申请公租房收入明细没有怎么办 杭州公租房满3年怎么办 小学寄读不转学籍手续怎么办 炸东西的油糊了怎么办 赠送面积为违建怎么办 执法不管违建我怎么办 司法考试毕业院校写错了怎么办 家具店西安一直拖着不交货怎么办 派出所私自迁移了我家户口怎么办? 退房子不退押金怎么办 租房子中介不退押金怎么办 租房子押金不退怎么办 外地人签户口到武汉怎么办 开发商不给办土地证怎么办 房间里的油烟味怎么办 现金借款app还不了款怎么办 学校不允许实习生自己租房怎么办 盯盯拍开不了机怎么办 向私人借钱不还怎么办 微信好友借钱不还怎么办 支付宝借不了钱怎么办 支付宝借条关了怎么办 qq群不小心解散了怎么办 qq群解散了照片怎么办 出租屋没窗户很闷怎么办 二手房交税后房主不卖怎么办 二房东收不到租拖欠房租怎么办 房东不给换门锁怎么办 租房到期房东联系不到租客怎么办 廉租房名下有车怎么办 路边停车收忘记交费怎么办 考编忘记交费了怎么办 深圳公租房入库了接下来怎么办 公租房5年以后怎么办