Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> [Android]ListView性能優化之視圖緩存

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

編輯:Android開發實例

前言

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

正文

  一、准備
 

    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 內存洩漏調試

  

結束

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved