[Android]ListView性能優化之視圖緩存

前言html

  ListView是Android中最經常使用的控件,經過適配器來進行數據適配而後顯示出來,而其性能是個很值得研究的話題。本文與你一塊兒探討Google I/O提供的優化Adapter方案,歡迎你們交流。
 java

 

聲明android

  歡迎轉載,但請保留文章原始出處:)
 緩存

  農民伯伯: http://over140.blog.51cto.com/  ide

正文性能

  1、準備
 測試

    1.1  瞭解關於Google IO大會關於Adapter的優化,參考如下文章:優化

      Android開發之ListView 適配器(Adapter)優化this

      Android開發——09Google I/O之讓Android UI性能更高效(1)idea

      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見文章末尾下載。

 

  2、測試
 

    測試方法:手動滑動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、總結

    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次數,可是從測試結果來看效果並不理想,這裏再也不作進一步的測試。

 

  5、推薦文章

    Android,誰動了個人內存(1)

    Android 內存泄漏調試

 

   6、後期維護

           2011-3-30  參見這裏(http://www.javaeye.com/topic/971782)的討論,據此將計劃寫續篇。
 

 

結束

  對於Google I/O大會這個優化方案一直抱遲疑態度,此番測試總算是有了更進一步的瞭解,歡迎你們先測試後交流,看看還有什麼辦法可以再優化一點。  

相關文章
相關標籤/搜索