Android-ListView優化常見的三種方式
優化原理
使用ListView時儘可能的少去執行Layout的Inflate,只渲染和佈置那些在可視範圍內,或者即將出現在可視範圍內的Item
第一
Layout的Inflate是消耗資源巨大的程式碼。即使,Layout檔案已經被高效的解析程式轉換為了二進位制程式碼。Infalte操作依舊需要徹底包含整個XML程式碼樹,而且還要例項化相應的View。在Android 的原始碼中,ListView通過View回收機制解決了這個問題。這樣就可以非常簡單的通過可回收的View設定每個Item的內容。而不用,為每一個Item都Inflate Layout 。
第二
通過ListView的View回收機制。在可視範圍上面或者下面的View加入到回收池中。當在可視範圍內的View被移出可視範圍內時,其也會被新增到回收池中。以這種方式ListView只需佔用非常少的記憶體幾可以儲存可視範圍內的View和回收池中的View。ListView的View回收機制以兩種不同的方式提供可回收的View即從上往下提供,和從下往上提供。採取何種方式,取決於滑動的方式。下面這張圖展示了,當你下滑時ListView的View回收機制所做的工作。
當滑動ListView時,ListView動態的提供可回收的View。因此,如何使Adapter的getView()變得儘可能輕巧成為了關鍵所在。
方式一:使用可回收的View
每當ListView需要顯示一條新的Item的時候,它會回撥你的Adapter的getView()方法。getView()方法有三個引數:Item的位置,convertView,Item的上級容器。
引數convertView實際上就是一個之前提到的可回收的View。當ListView要回收這個View的時候,它的資料就會被清空。因此,當convertView不為null的時候,只需要將資料填充到裡面,而不用Inflate一個新的View。
Adapter中的getView()方法的程式碼應該像下面這樣設計:
public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.your_layout, null); } TextView text = (TextView) convertView.findViewById(R.id.text); text.setText("Position " + position); return convertView; }
方式二:使用ViewHolder
在一個已經Inflate的Layout中尋找View是Android開發中非常普遍的操作。這通常通過View的findViewById方法來實現。這個方法會遞迴整個View樹,以尋找那個與ID匹配的View。在靜態的程式碼中使用findViewById()還是非常棒的,但是,如你看到的那樣。當滑動ListView的時候,ListView會非常頻繁的回撥Adapter的getView()方法。這就可能在不知不覺中影響ListView的滑動效能。尤其發生在你的Item的Layout非常的複雜的時候。
ViewHolder就是用來儲存那些在你的getView()方法中呼叫findViewById()方法得到的View。ViewHolder是一個非常輕巧的內部類。它儲存那些Item內部的View的直接引用。然後可以在Inflate結束之後,將ViewHolder物件儲存在Item的tag當中。以這種方式,你只需要在第一次建立Item的時候呼叫findViewById就可以了。
下面就是使用ViewHolder提高ListView的程式碼:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.your_layout, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = convertView.getTag();
}
holder.text.setText("Position " + position);
return convertView;
}
private static class ViewHolder {
public TextView text;
}
方式三:使用非同步載入
在Android的App中,ListView使用像圖片這樣耗費資源的Item是非常普遍的。在你的Adapter中使用drawable資源是非常棒的,因為Android記憶體的程式碼會快取這些資源。但是,你可能會想要使用更加靈活的內容。比如來自本地裝置或者來自網路的縮圖或者圖片之類的。在這種情況下,你可能不想要直接的在你的Adapter的getView()中載入這些資源。因為阻塞UI執行緒會造成ANR異常。同時這麼做也使得你的ListView滑動的更加平滑。
你所要做的事情就是讓那些需要IO操作或者耗費CPU資源的操作在一個額外的執行緒中執行。為了實現這個目的,你任然需要遵循ListView的View回收機制的規則。例如:當你的Adapter的getView()正在使用AsyncTask載入一張圖片。需要這張圖片的Item可能已經在圖片載入完成之前就已經被ListView的View回收機制回收了。因此你必須要知道,當你完成非同步載入的時候,對應的Item是否已經被回收了。
一個簡單的方法是,為你的AsyncTask提供一個識別資訊用以區別其對應的Item。這樣,當你的AsyncTask完成載入工作的時候,就可以判斷對應的Item還是不式最初的那個Item。事實上存在許多方式實現這個功能,下面的程式碼只是其中最為簡單的一種:public View getView(int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
...
holder.position = position;
new ThumbnailTask(position, holder)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
return convertView;
}
private static class ThumbnailTask extends AsyncTask {
private int mPosition;
private ViewHolder mHolder;
public ThumbnailTask(int position, ViewHolder holder) {
mPosition = position;
mHolder = holder;
}
@Override
protected Cursor doInBackground(Void... arg0) {
// Download bitmap here
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (mHolder.position == mPosition) {
mHolder.thumbnail.setImageBitmap(bitmap);
}
}
}
private static class ViewHolder {
public ImageView thumbnail;
public int position;
}