看到一篇關于不錯的文章,簡單翻譯了下。原文位址:http://leftshift.io/6-ways-to-make-your-lists-scroll-faster-than-the-wind
快樂與悲傷共存。開發一款App總是很有趣,然而比較傷心的是拿着一款舊的三星測試機的你,得到一款新的MotoX卻發現你的App在上面運作的總是不流暢。第一眼看到App如此卡頓,我真的流淚了。此時盡管夜已深,我依然決定找出fix的辦法。
以下是我找出一些條目:
1>:在Adapter中getView方法裡面,減少條件判斷;
2>:減少GC操作;
3>:避免在滑動時加載圖檔;
4>:将scrollingCache跟animateCache設定為false;
5>:簡化你的list View布局層次;
6>:使用ViewHolder模式;
如果這些你全都熟悉,可能你沒有必要再讀下文了。不管怎樣,如果還不是完全了解,請接着往下看。(這裡給出一些建議。這裡給出的方法唯一的解決方案。有些時候它能夠起作用,有時需要你嘗試其它方案。這些都取決于你的代碼。寫這些方案的目的是幫助你的App更流暢)
1:在Adapter中getView方法裡面,減少條件判斷
(在你的Adapter中,可能需要清理某些狀态資訊。每次當你的螢幕上需要顯示一項時這個方法都會被調用。是以這裡定義的結構集,在每次App運作時都會進行大量調用。是以此處減少條件判斷比較有意義。現在問題是不能在這裡做,在哪兒又比較合适呢?答案很簡單,如果你正在使用一個可序列化的類來擷取其值,你隻需要将條件判斷移動到類裡面,将值存在裡面即可。通過這種方式,條件判斷僅僅隻需執行一次,下面是一個簡單的例子)
這裡是處理備援之前的View代碼(這裡有很複雜的條件判斷,我們移除了條件判斷,使其更便于了解)
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
Object current_event = mObjects.get(position);
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.row_event, null);
holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// If you have conditions like below then you may face problem
if (doesSomeComplexChecking()) {
holder.ThreeDimention.setVisibility(View.VISIBLE);
} else {
holder.ThreeDimention.setVisibility(View.GONE);
}
// This method is setting layout parameters each time when getView is getting called which is wrong.
RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
holder.EventPoster.setLayoutParams(imageParams);
return convertView;
}
下面是更新後的getView代碼:
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
Object current_event = mObjects.get(position);
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.row_event, null);
holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
// This will now execute only for the first time of each row
RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
holder.EventPoster.setLayoutParams(imageParams);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// This way you can simply avoid conditions
holder.ThreeDimension.setVisibility(object.getVisibility());
return convertView;
}
2:檢查和減少你logs中GC的警告
當大量的建立跟銷毀對象時,GC将頻繁的出現。是以一般情況在getView中不建立大量的對象;更好的建議是-除了ViewHolder不建立任何對象。如果在Log中頻繁出現“GC已經釋放了記憶體”,那說明你的代碼中存在嚴重的問題,你有必要按照下面的方法檢查下:
1>:清單中每項的布局層次
2>:getView中的條件判斷是否較多
3>:每項View的屬性
3:避免在滑動的時候加載圖檔
當使用者快速滑動的時候,應用程式正在下載下傳圖檔,此時應該停止加載圖檔,保證滑動的流暢性;當滑動停止時再加載圖檔。這種方式的效果很明顯。
同時,Google也給出了示例代碼,參考https://code.google.com/p/iosched/
對于那些讨厭打開連接配接并下載下傳代碼的人,下面是一小段代碼,代碼說明了如何添加一個scroll listener來stop跟start加載圖檔隊列
<span style="font-size:18px;">listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView listView, int scrollState) {
// Pause disk cache access to ensure smoother scrolling
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
imageLoader.stopProcessingQueue();
} else {
imageLoader.startProcessingQueue();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});</span>
4:将scrollingCache跟animateCache設定為false
ListView有很多的屬性,以下是一些詳細的說明,參考:http://stackoverflow.com/questions/15570041/scrollingcache
scrolling cache是一個基本的drawing cache
在Android中,你可以請求一個View将正在繪制的緩存存儲在drawing cache中(比如Bitmap)。通常,drawing cache不可用,因為它會占用記憶體;但是你也可以通過setDrawingCacheEnabled或者hardware layers(setLayerType)來請求建立一個drawing cache。
為什麼這個比較有用?因為在View架構中,使用drawing cache能夠讓你的View得以比較平滑的繪制。
這類動畫也能夠被硬體加速,因為渲染系統能夠擷取該Bitmap并且作為texture(如果使用了硬體層)傳遞給GPU,能夠做更快的矩陣操作(比如:改變alpha值、旋轉、移動)。
在ListView中,當你快速滑動時,其實本質上是對ListView中View做動畫效果(不管是向上還是向下移動),ListView對可見的子View(靠近邊緣的可見子View)使用drawing cache使其能夠更快的進行動畫操作。
使用drawing cache有什麼缺點嗎?答案是的,由于使用drawing cache需要消耗記憶體,這就是為什麼預設是關閉的原因。對于ListView來說,隻要你Touch 或者移動一點點(跟滑動做差別),緩存就自動建立了。換句話說,當ListView認為你要滑動 or 快速滑動時,它将為你建立一個scrolling cache來確定更快的scroll or fling 動畫移動。
同樣,有時AnimationCache也有不好的地方(個人認為它導緻了頻繁的GC)
對于子View,布局動畫應該建立一個drawing cache。盡管動畫緩存會占用更多的記憶體且需要更長初始化,但是它提供了更好的性能。動畫緩存預設可用。動畫緩存參考Google文檔。
這裡是我們将要在ListView中修改的。原始代碼如下:
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/list_background_color"
android:dividerHeight="0dp"
android:listSelector="#00000000"
android:smoothScrollbar="true" />
下面是修改後的代碼:
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/list_background_color"
android:dividerHeight="0dp"
android:listSelector="#00000000"
android:smoothScrollbar="true"
android:scrollingCache="false"
android:animationCache="false" />
5:簡化LIstView每項布局的層次
你需要保證清單view布局盡可能的小。布局的層級跟深度直接跟view的測量、繪制相關(可以使用Hierarchy Viewer工具檢視)。後續部落格可能會繼續讨論該知識點。
6:使用view holder模式
對于初級開發者,這是一個經常犯的錯誤。第一次你的App表現正常,當快速滑動時,你的App将crash,出現這個問題的原因是因為,當你快速滑動時,其View每次都需要inflate,導緻性能降低。正确的做法是使用VIew Holder模式,将View存儲起來。具體參考文章1,文章2。
如果有其它技巧,歡迎補充。謝謝~~~