前言html
ListView是Android中最經常使用的控件,經過適配器來進行數據適配而後顯示出來,而其性能是個很值得研究的話題。本文與你一塊兒探討Google I/O提供的優化Adapter方案,歡迎你們交流。
java
正文性能
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 |
按照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大會這個優化方案一直抱遲疑態度,此番測試總算是有了更進一步的瞭解,歡迎你們先測試後交流,看看還有什麼辦法可以再優化一點。