[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大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。 


转载:http://www.cnblogs.com/over140/archive/2011/03/23/1991100.html

目录
相关文章
|
3月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
55 1
|
4月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
52 3
|
6月前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
6月前
|
测试技术 Android开发 Python
探索软件测试的艺术:从基础到高级安卓应用开发中的自定义视图
【8月更文挑战第29天】在软件开发的世界中,测试是不可或缺的一环。它如同艺术一般,需要精细的技巧和深厚的知识。本文旨在通过浅显易懂的语言,引领读者从软件测试的基础出发,逐步深入到更复杂的测试策略和工具的使用,最终达到能够独立进行高效测试的水平。我们将一起探索如何通过不同的测试方法来确保软件的质量和性能,就像艺术家通过不同的色彩和笔触来完成一幅画作一样。
|
6月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
56 2
|
4月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
125 15
Android 系统缓存扫描与清理方法分析
|
3月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
3月前
|
存储 缓存 监控
|
5月前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
81 10
|
5月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
97 8

热门文章

最新文章

  • 1
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    22
  • 2
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    19
  • 3
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    51
  • 4
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    35
  • 5
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    70
  • 6
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    108
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29
  • 8
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
    256
  • 9
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
    69
  • 10
    【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    36