zoukankan      html  css  js  c++  java
  • [Android]ListView性能优化之视图缓存

    前言

      ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。

    声明

      欢迎转载,但请保留文章原始出处:)

        博客园:http://www.cnblogs.com

        农民伯伯: http://over140.cnblogs.com

    正文

      一、准备

        1.1  了解关于Google IO大会关于Adapter的优化,参考以下文章:

          Android开发之ListView 适配器(Adapter)优化

          Android开发——09Google I/O之让Android UI性能更高效(1)

          PDF下载:Google IO.pdf

        1.2  准备测试代码:

          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);
        }

          代码说明:模拟一千条数据,TestAdapter继承自BaseAdapter,main.xml见文章末尾下载。

      二、测试

        测试方法:手动滑动ListView至position至50然后往回滑动,充分利用convertView不等于null的代码段。

        2.1  方案一

          按照Google I/O介绍的第二种方案,把item子元素分别改为4个和10个,这样效果更佳明显。

          2.1.1  测试代码

            private int count = 0;
            
    private long sum = 0L;
            @Override
            
    public View getView(int position, View convertView, ViewGroup parent) {
                
    //开始计时
                long startTime = System.nanoTime();
                
                
    if (convertView == null) {
                    convertView 
    = mInflater.inflate(R.layout.list_item_icon_text,
                            
    null);
                }
                ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
                ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
                ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
                ((TextView) convertView.findViewById(R.id.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;
            }

           2.1.2  测试结果(微秒除以1000,见代码)

    次数

    4个子元素

    10个子元素

    第一次

     366

    723

    第二次

    356

    689

    第三次

     371

    692

    第四次

    356

    696

    第五次

     371

    662

     
        2.2  方案二

          按照Google I/O介绍的第三种方案,是把item子元素分别改为4个和10个。

          2.2.1  测试代码

            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;
        }

           2.2.2  测试结果(微秒除以1000,见代码)

    次数

    4个子元素

    10个子元素

    第一次

     311

     417

    第二次

     291

     441

    第三次

     302

     462

    第四次

     286

     444

    第五次

     299

     436

        2.3  方案三

          此方案为“Henry Hu”提示,API Level 4以上提供,这里顺带测试了一下不使用静态内部类情况下性能。

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

                
    if (convertView == null) {
                    convertView 
    = mInflater.inflate(R.layout.list_item_icon_text, null);
                    convertView.setTag(R.id.icon1, convertView.findViewById(R.id.icon1));
                    convertView.setTag(R.id.text1, convertView.findViewById(R.id.text1));
                    convertView.setTag(R.id.icon2, convertView.findViewById(R.id.icon2));
                    convertView.setTag(R.id.text2, convertView.findViewById(R.id.text2));
                }
                ((ImageView) convertView.getTag(R.id.icon1)).setImageResource(R.drawable.icon);
                ((ImageView) convertView.getTag(R.id.icon2)).setImageResource(R.drawable.icon);
                ((TextView) convertView.getTag(R.id.text1)).setText(mData[position]);
                ((TextView) convertView.getTag(R.id.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+ ":" + nullcount);// 显示统计结果
                return convertView;
            }

            2.3.2  测试结果(微秒除以1000,见代码)

            第一次:450

            第二次:467

            第三次:472

            第四次:451

            第五次:441
      四、总结

        4.1  首先有一个认识是错误的,我们先来看截图:

           

          

          可以发现,只有第一屏(可视范围)调用getView所消耗的时间远远多于后面的,通过对

    convertView == null内代码监控也是同样的结果。也就是说ListView仅仅缓存了可视范围内的View,随后的滚动都是对这些View进行数据更新。不管你有多少数据,他都只用ArrayList缓存可视范围内的View,这样保证了性能,也造成了我以为ListView只缓存View结构不缓存数据的假相(不会只有我一人这么认为吧- - #)。这也能解释为什么GOOGLE优化方案一比二高很多的原因。那么剩下的也就只有findViewById比较耗时了。据此大家可以看看AbsListView的源代码,看看
    obtainView这个方法内的代码及RecycleBin这个类的实现,欢迎分享。

          此外了解这个原理了,那么以下代码不运行你可能猜到结果了:

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

          没错,你会发现滚动时会重复显示第一屏的数据!

          子控件里的事件因为是同一个控件,也可以直接放到convertView == null 代码块内部,如果需要交互数据比如position,可以通过tag方式来设置并获取当前数据。

        4.2  本文方案一与方案二对比

          这里推荐如果只是一般的应用(一般指子控件不多),无需都是用静态内部类来优化,使用第二种方案即可;反之,对性能要求较高时可采用。此外需要提醒的是这里也是用空间换时间的做法,View本身因为setTag而会占用更多的内存,还会增加代码量;而findViewById会临时消耗更多的内存,所以不可盲目使用,依实际情况而定。

        4.3  方案三

          此方案为“Henry Hu”提示,API Level 4以上支持,原理和方案三一致,减少findViewById次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。

      五、推荐文章

        Android,谁动了我的内存(1)

        Android 内存泄漏调试

      六、后期维护

               2011-3-30  参见这里(http://www.javaeye.com/topic/971782)的讨论,据此将计划写续篇。

    结束

      对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。

  • 相关阅读:
    h5 input调起摄像头、摄像机、录音机
    基数排序
    快速排序 && 希尔排序 && 插入排序
    堆排序
    归并排序(Merge sort)
    动态规划:背包问题
    Chap5: question: 29
    排列 && 组合
    Chap4: question: 19
    Chap3: question: 11
  • 原文地址:https://www.cnblogs.com/over140/p/1991100.html
Copyright © 2011-2022 走看看