1.2 準備測試代碼:
Activity
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 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 |
按照Google I/O介紹的第三種方案,是把item子元素分別改爲4個和10個。
2.2.1 測試代碼
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 |
此方案爲“Henry Hu”提示,API Level 4以上提供,這裏順帶測試了一下不使用靜態內部類情況下性能。
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
4.1 首先有一個認識是錯誤的,我們先來看截圖:
可以發現,只有第一屏(可視範圍)調用getView所消耗的時間遠遠多於後面的,通過對
此外瞭解這個原理了,那麼以下代碼不運行你可能猜到結果了:
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次數,但是從測試結果來看效果並不理想,這裏不再做進一步的測試。