2012年4月15日 星期日

ListView, CursorAdapter, ViewHolder

這次來說說CursorAdapter

當資料來源是cursor,顯示界面為ListView就可以很方便的使用CursorAdapter

extends CursorAdapter的時候需要override bindView()以及newView()

bindView會在顯示每一個item的時候被呼叫

newView則會在需要的時候被呼叫,譬如說第一次建立此位置的View時,或是此位置的View已經被回收時
@Override public void bindView(View view, Context context, Cursor cursor) { if (view == null || cursor == null) return; String bucketName = null, displayName = null; ... } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); return inflater.inflate(R.layout.media_item, null); }
如果總共有100個item,銀幕上最多顯示8個item

那畫面顯示出來時newView()/bindView()就會分別被呼叫8次,滑動時就只會呼叫bindView(),偶而才會呼叫newView()
一般如果是直接使用BaseAdapter,會override getView()這個method

通常寫法會是判斷傳入的view是不是null,null則inflate一個新的view,非null則直接拿來用

CursorAdapter也是extends自BaseAdapter所以也會有getView()這個method

網路上查到的資料是CursorAdapter裡getView()的實作就是判斷是否需要建立view,需要則呼叫newView(),不需要則直接呼叫bindView()

由於最近在做performance tuning,所以也看到一個ViewHolder的用法,主要是減少findViewById()運算
class ViewHolder { TextView text1; TextView text2; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.media_item, null); ViewHolder vh = new ViewHolder(); vh.text1 = (TextView) view.findViewById(R.id.textView1); vh.text2 = (TextView) view.findViewById(R.id.textView2); view.setTag(vh); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { if (view == null || cursor == null) return; String bucketName = null, displayName = null; bucketName = cursor.getString(2); displayName = cursor.getString(3); ViewHolder vh = (ViewHolder) view.getTag(); vh.text1.setText(bucketName); vh.text2.setText(displayName); }
View有一個setTag(Object)的method,newView()的時候,先將之後會用到的view reference都存在ViewHolder,再set到inflate出的view中

bindView()的時候就不需要再執行findViewById,直接getTag()就可以將各個view的reference取出來用

當資料數量很多的時候,透過這種寫法應該能提升不少performance

這次tuning performance也發生了一個小插曲

當我把程式改完,理論上會比原本方法好很多,但結果卻發現,比原本的方式速度還慢

找了好久,程式比了又比,這邊mark完比那邊,最後終於發現root cause

程式是要將cursor中的資料轉換成local的list,裡面有一個欄位為date,在database中是一串數字

get出來透過下面的方式轉成字串存在local list中
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss", Locale.US); String date = dateFormat.format(new Date(databaseDate));
databaseDate為datebase中的數字

由於我測試的資料有2000多筆,所以這樣的轉換作了2000多次

本來理想狀況改完之後的速度應該比原先的快,結果竟然慢了2/3倍

表示SimpleDateFormat的轉換實際上還蠻耗時的

後來改成直接在local list存數字,當需要顯示date字串時在進行轉換,速度跟飛的一樣!!!

以後進行performance tuning時要注意這些被忽略的小地方阿!!!

1 則留言: