我們?cè)O(shè)置或者優(yōu)化ListView的性能很多時(shí)候都是在getView中完成的檩奠,反過(guò)來(lái)說(shuō)就是很多性能問(wèn)題都是由于沒(méi)有正確使用getView造成的骄酗。
public View getView(int position, View convertView, ViewGroup parent)
那么問(wèn)題就來(lái)了。
在一次顯示ListView的界面時(shí),getView會(huì)被執(zhí)行幾次拙友?
在繪制ListView前往往要計(jì)算它的高度,所以一個(gè)ListView界面上可以看到6個(gè)ItemView,但是getView的執(zhí)行次數(shù)卻有可能是12次其做,多出的次數(shù)用來(lái)計(jì)算高度(這個(gè)可以通過(guò)設(shè)置ListView的height為0來(lái)避免)渊季。所以要避免在getView中進(jìn)行邏輯運(yùn)算,兩次計(jì)算同一邏輯完全是浪費(fèi)。
每次getView執(zhí)行時(shí)間有多久黎茎?
1秒之內(nèi)屏幕大概可以完成30幀的繪制,人才能看到它比較流暢,每幀可使用的時(shí)間:1000ms/30 = 33.33 ms炊林,每個(gè)ListView一般要顯示6個(gè)ListItem棺榔,加上1個(gè)重用convertView:33.33ms/7 = 4.76ms,即是說(shuō)缩幸,每個(gè)getView要在4.76ms內(nèi)完成工作才會(huì)較流暢难咕,但是事實(shí)上爆土,每個(gè)getView間的調(diào)用也會(huì)有一定的間隔(有可能是由于handler在處理別的消息)步势,UI的handler處理不好的話氧猬,這個(gè)間隔也可難會(huì)很大(0ms-200ms)桑腮。結(jié)論就是提陶,留給getView使用的時(shí)間應(yīng)該在4ms之內(nèi)撑柔,如果不能控制在這之內(nèi)的話,ListView的滑動(dòng)就會(huì)有卡頓的現(xiàn)象累舷。
(轉(zhuǎn)載:Android面試一天一題(11 Day) —— goeasyway)
有哪些常見的優(yōu)化方案浩考?
1. 使用ConvertView
也是最普通的優(yōu)化,就在MyAdapter類中的getView方法中被盈,我們注意到析孽,上面的寫法每次需要一個(gè)View對(duì)象時(shí),都是去重新inflate一個(gè)View出來(lái)返回去只怎,沒(méi)有實(shí)現(xiàn)View對(duì)象的復(fù)用袜瞬,而實(shí)際上對(duì)于ListView而言,只需要保留能夠顯示的最大個(gè)數(shù)的view即可身堡,其他新的view可以通過(guò)復(fù)用的方式使用消失的條目的view邓尤,而getView方法里也提供了一個(gè)參數(shù):convertView,這個(gè)就代表著可以復(fù)用的view對(duì)象贴谎,當(dāng)然這個(gè)對(duì)象也可能為空汞扎,當(dāng)它為空的時(shí)候,表示該條目view第一次創(chuàng)建擅这,所以我們需要inflate一個(gè)view出來(lái),所以在這里澈魄,我們使用下面這種方式來(lái)重寫getView方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
// 判斷convertView的狀態(tài),來(lái)達(dá)到復(fù)用效果
if (null == convertView) {
//如果convertView為空仲翎,則表示第一次顯示該條目痹扇,需要?jiǎng)?chuàng)建一個(gè)view
view = View.inflate(MainActivity.this, R.layout.listview_item,null);
} else {
//否則表示可以復(fù)用convertView
view = convertView;
}
// listview_item里只有一個(gè)textview
TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
tv_item.setText(list.get(position));
return view;
}
2. 使用View Holder模式
經(jīng)過(guò)上面的優(yōu)化之后,我們不需要每一個(gè)view都重新生成了溯香。下面我們來(lái)解決下一個(gè)每一次都需要做的工作鲫构,那就是view中組件的查找:
TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
實(shí)際上,findViewById是到xml文件中去查找對(duì)應(yīng)的id玫坛,可以想象如果組件多的話也是挺費(fèi)事的芬迄,如果我們可以讓view內(nèi)的組件也隨著view的復(fù)用而復(fù)用,就能對(duì)ListView進(jìn)行很好的優(yōu)化
private static class ViewHolder {
private TextView tvHolder;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
// 判斷convertView的狀態(tài)昂秃,來(lái)達(dá)到復(fù)用效果
if (null == convertView) {
// 如果convertView為空禀梳,則表示第一次顯示該條目,需要?jiǎng)?chuàng)建一個(gè)view
view = View.inflate(MainActivity.this, R.layout.listview_item, null);
//新建一個(gè)viewholder對(duì)象
holder = new ViewHolder();
//將findviewbyID的結(jié)果賦值給holder對(duì)應(yīng)的成員變量
holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);
// 將holder與view進(jìn)行綁定
view.setTag(holder);
} else {
// 否則表示可以復(fù)用convertView
view = convertView;
holder = (ViewHolder) view.getTag();
}
// 直接操作holder中的成員變量即可肠骆,不需要每次都findViewById
holder.tvHolder.setText(list.get(position));
return view;
}
ps:
這里的ViewHolder類需要不需要定義成static算途,根據(jù)實(shí)際情況而定,如果item不是很多的話蚀腿,可以使用嘴瓤,這樣在初始化的時(shí)候扫外,只加載一次,可以稍微得到一些優(yōu)化廓脆。
不過(guò)筛谚,如果item過(guò)多的話,建議不要使用停忿。因?yàn)閟tatic是Java中的一個(gè)關(guān)鍵字驾讲,當(dāng)用它來(lái)修飾成員變量時(shí),那么該變量就屬于該類席赂,而不是該類的實(shí)例吮铭。所以用static修飾的變量,它的生命周期是很長(zhǎng)的颅停,如果用它來(lái)引用一些資源耗費(fèi)過(guò)多的實(shí)例(比如Context的情況最多)谓晌,這時(shí)就要盡量避免使用了。
3. 分批加載與分頁(yè)加載相結(jié)合
我們需要進(jìn)行分批加載癞揉,比如說(shuō)1000條新聞的List集合纸肉,我們一次加載20條,等到用戶翻頁(yè)到底部的時(shí)候喊熟,我們?cè)偬砑酉旅娴?0條到List中毁靶,再使用Adapter刷新ListView,這樣用戶一次只需要等待20條數(shù)據(jù)的傳輸時(shí)間逊移,不需要一次等待好幾分鐘把數(shù)據(jù)都加載完再在ListView上顯示预吆。其次這樣也可以緩解很多條新聞一次加載進(jìn)行產(chǎn)生OOM應(yīng)用崩潰的情況。
實(shí)際上胳泉,分批加載也不能完全解決問(wèn)題拐叉,因?yàn)殡m然我們?cè)诜峙幸淮沃辉黾?0條數(shù)據(jù)到List集合中,然后再刷新到ListView中去扇商,假如有10萬(wàn)條數(shù)據(jù)凤瘦,如果我們順利讀到最后這個(gè)List集合中還是會(huì)累積海量條數(shù)的數(shù)據(jù),還是可能會(huì)造成OOM的情況案铺,這時(shí)候我們就需要用到分頁(yè)蔬芥,比如說(shuō)我們將這10萬(wàn)條數(shù)據(jù)分為1000頁(yè),每一頁(yè)100條數(shù)據(jù)控汉,每一頁(yè)加載時(shí)都覆蓋掉上一頁(yè)中List集合中的內(nèi)容笔诵,然后每一頁(yè)內(nèi)再使用分批加載,這樣用戶的體驗(yàn)就會(huì)相對(duì)好一些姑子。
除此之前還有一些優(yōu)化建議:
- 使用異步線程加載圖片(一般都是直接使用圖片庫(kù)加載乎婿,如Glide, Picasso);
- 在adapter的getView方法中盡可能的減少邏輯判斷街佑,特別是耗時(shí)的判斷谢翎;
- 避免GC(可以從LOGCAT查看有無(wú)GC的LOG)捍靠;
- 在快速滑動(dòng)時(shí)不要加載圖片;
- 將ListView的scrollingCache和animateCache這兩個(gè)屬性設(shè)置為false(默認(rèn)是true);
- 盡可能減少List Item的Layout層次(如可以使用RelativeLayout替換LinearLayout森逮,或使用自定的View代替組合嵌套使用的Layout)榨婆;
(轉(zhuǎn)載:Android面試一天一題(11 Day) —— goeasyway)