android ListView 性能優化

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章