[Android]ListView性能优化之视图缓存

简介:

一、准备

    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 内存泄漏调试

本文转自博客园农民伯伯的博客,原文链接:[Android]ListView性能优化之视图缓存,如需转载请自行联系原博主。

目录
相关文章
|
20天前
|
缓存 监控 Android开发
安卓应用性能优化的实用策略
【4月更文挑战第2天】 在竞争激烈的应用市场中,一款应用的性能直接影响用户体验和市场表现。本文针对安卓平台,深入探讨了性能优化的关键要素,包括内存管理、代码效率、UI渲染和电池使用效率。通过分析常见的性能瓶颈,并提供针对性的解决策略,旨在帮助开发者构建更加流畅、高效的安卓应用。
|
26天前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
112 0
|
3月前
|
Java 调度 数据库
Android 性能优化: 如何进行多线程编程以提高应用性能?
Android 性能优化: 如何进行多线程编程以提高应用性能?
47 0
|
15天前
|
监控 API Android开发
构建高效安卓应用:探究Android 12中的新特性与性能优化
【4月更文挑战第8天】 在本文中,我们将深入探讨Android 12版本引入的几项关键技术及其对安卓应用性能提升的影响。不同于通常的功能介绍,我们专注于实际应用场景下的性能调优实践,以及开发者如何利用这些新特性来提高应用的响应速度和用户体验。文章将通过分析内存管理、应用启动时间、以及新的API等方面,为读者提供具体的技术实现路径和代码示例。
|
29天前
|
缓存 前端开发 Android开发
构建高效Android应用:从设计原则到性能优化
随着移动设备成为我们日常生活不可或缺的一部分,开发一个流畅且响应迅速的Android应用变得至关重要。本文将探讨如何通过遵循Android设计原则和实施细致的性能优化策略来构建高效的Android应用程序。我们将深入分析应用架构的选择、内存管理的要点以及UI设计的优化,旨在为开发人员提供一套实用的指导方针,帮助他们提升应用的整体性能和用户体验。
|
1月前
|
监控 Java Android开发
构建高效Android应用:从内存管理到性能优化
【2月更文挑战第30天】 在移动开发领域,打造一个流畅且响应迅速的Android应用是每个开发者追求的目标。本文将深入探讨如何通过有效的内存管理和细致的性能调优来提升应用效率。我们将从分析内存泄露的根本原因出发,讨论垃圾回收机制,并探索多种内存优化策略。接着,文中将介绍多线程编程的最佳实践和UI渲染的关键技巧。最后,我们将通过一系列实用的性能测试工具和方法,帮助开发者监控、定位并解决性能瓶颈。这些技术的综合运用,将指导读者构建出更快速、更稳定、用户体验更佳的Android应用。
|
1月前
|
Java 定位技术 Android开发
安卓应用性能优化实践
【2月更文挑战第26天】在竞争激烈的应用市场中,一个高效流畅的安卓应用是吸引和留住用户的关键。本文将深入探讨安卓应用性能优化的多个方面,包括内存管理、多线程处理、UI渲染效率以及电池使用优化。通过实例分析和最佳实践的分享,旨在帮助开发者构建更快、更稳定、更节能的安卓应用。
|
1月前
|
设计模式 缓存 Android开发
深入理解Android应用性能优化
【2月更文挑战第18天】在移动开发领域,应用性能是用户体验的关键因素之一。特别是对于安卓设备而言,由于硬件配置的多样性,确保应用在不同设备上都能流畅运行是一项挑战。本文将探讨Android应用的性能优化策略,包括内存管理、UI渲染、多线程处理以及电池效率等方面。通过实例和最佳实践,我们将展示如何诊断性能瓶颈,并提供解决方案来改善应用响应速度和稳定性。
|
1月前
|
存储 缓存 NoSQL
Redis缓存设计与性能优化(一)
Redis缓存设计与性能优化(一)
|
2月前
|
存储 缓存 NoSQL
Redis--缓存设计与性能优化
Redis--缓存设计与性能优化

热门文章

最新文章