Android Performance of Listview |
Android Performance Listview ในการเขียน App บน Android และใช้การแสดงผลข้อมูลบนหน้าจอ Widgets ทีเราจะใช้บ่อย ๆ คือ ListView และบทความนี้เรามาเรียนรู้วิธีการปรับแต่ง Item ของ ListView ที่แสดงผลบนหน้าจอของ Application ให้สามารถใช้งานและเกิดประสิทธิภาพสูงสุดในการใช้งาน ป้องกันการเกิด Memory Leak หรือ Out of memory ในกรณีที่ข้อมูลมีจำนวนมาก
ทำความเข้าใจเกี่ยวกับ Item ของ ListView ?
ในการแสดง Item ของ ListView นั้นในกรณีที่ข้อมูลมีหลาย Items Record ตัว ListView จะไม่ได้แสดงข้อมูลทั้งหมดออกมาทาง ListView ในทันที แต่จะแสดงเท่าที่หน้าจอ Application เห็นเท่านั้น และเมื่อเลื่อน Scroll ขึ้นลง ListView ข้อมูลอื่น ๆ ค่อยแสดงข้อมูลออกมาก ซึ่งจะมีตัวแปร position เป็นตัวระบุตำแหน่งเมื่อมีการเลื่อนหน้าจอต่าง ๆ และเมื่อ item position นั้น ๆ อยู่ในตำแหน่งที่ปรากฏบนหน้าจอแสดงผล ก็จะมีการดึงข้อมูลออกมาแสดง ซึ่งจะทำงานแบบนี้ซ่ำ ๆ ทุกครั้งที่เลื่อนขึ้น เลื่อนลง แม้ว่าข้อมูลนั้นจะเคยถูกแสดงแล้วก็ตาม
ในการใช้ ListView จะใช้การสร้าง Custom Adapter เพื่อเชื่อมการทำงานระหว่าง ข้อมูลที่อยู่ในรูปแบบของ Cursor หรือ Array กับกับ Item ของ ListView ซึ่งจะสามารถใช้ Adapter หลายตัวเช่น BaseAdapter ในกรณีที่ใช้ BaseAdapter จะมี method ที่มีชื่อว่า getView() ใช้สำหรับกำหนดเงื่อนไขของการแสดงข้อมูลในแต่ล่ะ Item ซึ่ง method ของ getView() จะทำงานเป็น Loop เท่ากับจำนวน Item หรือ Rows ของข้อมูลที่ถูกส่งเข้ามา เช่น
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.activity_column, null);
}
// ColID
TextView txtID = (TextView) convertView.findViewById(R.id.ColID);
txtID.setText(MyArrList.get(position).get("ID") +".");
// ColCode
TextView txtCode = (TextView) convertView.findViewById(R.id.ColCode);
txtCode.setText(MyArrList.get(position).get("Code"));
// ColCountry
TextView txtCountry = (TextView) convertView.findViewById(R.id.ColCountry);
txtCountry.setText(MyArrList.get(position).get("Country"));
return convertView;
}
จาก Code จะเห็นว่า convertView คือ view ที่ของ Layout ที่เป็น Custom Column และทุก ๆ Loop ที่ทำงานก็จะมีการอ้างถึง Widgets ที่อยู่ใน view ของ convertView พร้อม ๆ กับการกำหนดค่าให้แต่ล่ะ Items ซึ่งจะทำงานซ้ำ ๆ ทุก ๆ Rows ของ Item
จะเห็นว่าจากตัวอย่างแรกจะมีการเรียกและสร้าง Layout ซ้ำ ๆ เพราะจะมีการ findViewById() ตัว Widgets ของ Custom Layout ซ้ำ ๆ ใน Loop ทุก ๆ รอบ โดยไม่จำเป็น และเมื่อปริมาณ Item Rows มีหลายรายการ อาจจะทำให้ประสิทธิ์ภาพของ ListView ลดลงได้ รวมถึงการทำงานก็จะช้าลงไปด้วย
เทคนิคที่นิยมใช้
เทคนิคที่ยิมใช้ในการเพิ่ม Performance ให้กับ Listview คือการสร้าง view holder จาก view ที่ถูกสร้างไว้อยู่แล้ว และเรียกใช้ view เดิม โดยการเปลี่ยนแปลงค่าแค่บางส่วนของ view ดูตัวอย่าง
public View getView(final int position, View convertView,
ViewGroup parent) {
// TODO Auto-generated method stub
CountryHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.activity_column, null);
holder = new CountryHolder();
holder.ID = (TextView) convertView.findViewById(R.id.ColID);
holder.Code = (TextView) convertView.findViewById(R.id.ColCode);
holder.Country = (TextView) convertView.findViewById(R.id.ColCountry);
convertView.setTag(holder);
} else {
holder = (CountryHolder) convertView.getTag();
}
holder.ID.setText(MyArrList.get(position).get("ID"));
holder.Code.setText(MyArrList.get(position).get("Code"));
holder.Country.setText(MyArrList.get(position).get("Country"));
return convertView;
}
จาก Code จะเห็นว่า convertView ในแรกสุดจะมีค่าเป็นว่าง และจะทำงานแค่ครั้งเดียว แต่ใน Loop ถัด ๆ ไปจะเป็นการเรียกค่าจาก holder ที่อยุ่ใน convertView และจะเปลี่ยนแปลงค่าแค่บางส่วนของ view เท่านั้น ซึ่งวิธีนี้จะเป็นการช่วยเพิ่มประสิทธิภาพได้อาจจะไม่มากเท่าไหร่ แต่ในกรณีที่ข้อมูลมีปริมาณมาก ก็น่าจะเป็นอีกทางเลือกหนึ่ง
ในการเขียนโปรแกรมบน Android กับข้อมูลที่มีปริมาณมาก ๆ ให้เกิดประสิทธิภาพสูงสุด ควรคำนึงตั้งแต่ขั้นตอนการนำมาข้อมูลมาแสดงผล เช่นอาจจะใช้การแบ่งหน้าข้อมูล หรือแสดงข้อมูลมาเฉพาะส่วน และให้ผู้ใช้ค่อย ๆ เลื่อนดูข้อมูลในการรายถัด ๆ ไป ตามตัวอย่างนี้
Android ListView Padding and Pagination
Android (ListView/GridView) get Result from Web Server and Paging Pagination
Example การใช้ convertView กับการปรับแต่ง Performance ของ Listview
activity_main.xml
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tableLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="ListView : "
android:layout_span="1"
android:textAppearance="?android:attr/textAppearanceMedium" />
</TableRow>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.1">
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<LinearLayout
android:id="@+id/LinearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="By.. ThaiCreate.Com" />
</LinearLayout>
</TableLayout>
activity_column.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/ColID"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="ID"/>
<TextView
android:id="@+id/ColCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Code"/>
<TextView
android:id="@+id/ColCountry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Country" />
</LinearLayout>
MainActivity.java
package com.myapp;
import java.util.ArrayList;
import java.util.HashMap;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
ArrayList<HashMap<String, String>> MyArrList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// listView1
final ListView lisView1 = (ListView)findViewById(R.id.listView1);
MyArrList = new ArrayList<HashMap<String, String>>();
HashMap<String, String> map;
/*** Rows 1 ***/
map = new HashMap<String, String>();
map.put("ID", "1");
map.put("Code", "TH");
map.put("Country", "Thailand");
MyArrList.add(map);
/*** Rows 2 ***/
map = new HashMap<String, String>();
map.put("ID", "2");
map.put("Code", "VN");
map.put("Country", "Vietnam");
MyArrList.add(map);
/*** Rows 3 ***/
map = new HashMap<String, String>();
map.put("ID", "3");
map.put("Code", "ID");
map.put("Country", "Indonesia");
MyArrList.add(map);
/*** Rows 4 ***/
map = new HashMap<String, String>();
map.put("ID", "4");
map.put("Code", "LA");
map.put("Country", "Laos");
MyArrList.add(map);
/*** Rows 5 ***/
map = new HashMap<String, String>();
map.put("ID", "5");
map.put("Code", "MY");
map.put("Country", "Malaysia");
MyArrList.add(map);
lisView1.setAdapter(new CountryAdapter(this));
}
public class CountryAdapter extends BaseAdapter
{
private Context context;
public CountryAdapter(Context c)
{
// TODO Auto-generated method stub
context = c;
}
public int getCount() {
// TODO Auto-generated method stub
return MyArrList.size();
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
public View getView(final int position, View convertView,
ViewGroup parent) {
// TODO Auto-generated method stub
CountryHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.activity_column, null);
holder = new CountryHolder();
holder.ID = (TextView) convertView.findViewById(R.id.ColID);
holder.Code = (TextView) convertView.findViewById(R.id.ColCode);
holder.Country = (TextView) convertView.findViewById(R.id.ColCountry);
convertView.setTag(holder);
} else {
holder = (CountryHolder) convertView.getTag();
}
holder.ID.setText(MyArrList.get(position).get("ID"));
holder.Code.setText(MyArrList.get(position).get("Code"));
holder.Country.setText(MyArrList.get(position).get("Country"));
return convertView;
}
}
public class CountryHolder {
TextView ID;
TextView Code;
TextView Country;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
Screenshot
แสดงผลบน Emulator
|