Android getView方法優化簡記
Android getView方法優化簡記
學習Android差不多半年,現在看來以前學習的終究太淺,重量不中質。看書也是囫圇吞棗,總想著看完再說,卻沒有想想自己究竟真正的掌握了什麼。最近一直在反思之前的學習歷程,這個getView方法的優化讓我印象頗深,網上的資料數不勝數,說是優化,不如說是一種應該如此的學法。雖然只是入門的知識,但是很多基礎教程上都沒有提及。把它寫下來,就是給自己提個醒,也算值得。
這裡以ListView的getView()為例,剛學習Android的時候,重寫ListView的Adapter時getView方法是這樣寫的:
@Override public View getView(int position, View convertView, ViewGroup parent) { View view = View.inflate(ctx, R.layout.layout_item, null); TextView tv1 = (TextView) view.findViewById(R.id.tv_1st); TextView tv2 = (TextView) view.findViewById(R.id.tv_2ed); tv1.setText(position + ""); tv2.setText(strs[new Random().nextInt(strs.length)]); return view; }
這種寫法就是一般的Android入門書裡的學法,當ListView顯示的資料較多時,在手機配置較差或模擬器中快速滑動列表時,特別容易出現記憶體溢位的錯誤,導致應用程式崩潰。
檢視log:
在Android原始碼或是開發中應該這樣寫
@Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; ViewHolder holder; if (convertView != null) { //複用控制元件 view = convertView; holder = (ViewHolder) view.getTag(); } else { //構造列表項控制元件,用ItemView進行快取 view = View.inflate(ctx, R.layout.layout_item, null); holder = new ViewHolder(); holder.tv1 = (TextView) view.findViewById(R.id.tv_1st); holder.tv2 = (TextView) view.findViewById(R.id.tv_2ed); view.setTag(holder); } //繫結資料 holder.tv1.setText("ID: " + position); holder.tv2.setText(strs[new Random().nextInt(strs.length)]); return view; } } //快取資料項控制元件物件 static class ViewHolder { TextView tv1; TextView tv2; }
這種寫法為每個類表象都inflate了一個View物件,當列表項很多時會導致子控制元件的數量急劇膨脹,耗費大量的記憶體資源,甚至導致應用崩潰。Android的介面卡在設計時,充分考慮了處理大規模資料的場景,為開發者提供瞭解決策略。在Adapter.getView方法中,有一個輸入引數convertView,用於快取最近一個失去可視狀態的列表控制元件物件。當用戶滾動列表時,處於可視狀態的列表項會變成不可視狀態,而不可視狀態的列表則可能會變成可視狀態。convertView便是用於快取失去可視狀態的列表項控制元件物件,通過Adapter.getVie方法傳回開發者手中,開發者可以複用這個控制元件物件重新繫結即將可視的列表項資料,從而避免了構造新列表控制元件的開銷。
另外,在使用convertView的時候,對列表項控制元件的子控制元件進行快取可以節省列表項資料項繫結的開銷,View.setTag方法可以將快取放在對應的列表項控制元件中,節省呼叫View.findViewById方法造成的浪費。
通過列表項複用,可以有效地提高列表項的效能,節約記憶體開銷,但同時它也增加了程式設計的複雜度,尤其是存在多種表項樣式時,這種複雜度的增加也尤為明顯。開發者需要根據列表項的數量和特徵,選擇合適的構造方法。
再看一下Android原始碼中的示例:
android/packages/apps/settings/src/com/android/settings
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls
// to findViewById() on each row.
AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
convertView = holder.rootView;
MyApplicationInfo info = getItem(position);
holder.appName.setText(info.label);
if (info.info != null) {
holder.appIcon.setImageDrawable(info.info.loadIcon(getPackageManager()));
holder.appSize.setText(info.info.packageName);
} else {
holder.appIcon.setImageDrawable(null);
holder.appSize.setText("");
}
holder.disabled.setVisibility(View.GONE);
holder.checkBox.setVisibility(View.GONE);
return convertView;
}
}
public class AppViewHolder {
public ApplicationsState.AppEntry entry;
public View rootView;
public TextView appName;
public ImageView appIcon;
public TextView appSize;
public TextView disabled;
public CheckBox checkBox;
static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.manage_applications_item, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
AppViewHolder holder = new AppViewHolder();
holder.rootView = convertView;
holder.appName = (TextView) convertView.findViewById(R.id.app_name);
holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard);
convertView.setTag(holder);
return holder;
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
return (AppViewHolder)convertView.getTag();
}
}
之前也曾經看過相關內容,可是卻由於思考上的惰性,導致之後的寫法還是剛接觸時的樣子。學習不能只是被動接受,學而不思則罔,古人說的太對了。