在进行AdapterView性能优化之前,有必要先了解下listview加载数据的原理。
一、原理:
ListView的三个元素:
ListView: 用来展示列表的View控件;
适配器Adapter: 用来把数据映射到ListView上;
数据: 具体的将被映射的字符串,图片等;
根据列表的适配器类型,列表分为四种:ArrayAdapter,SimpleAdapter,SimpleCursorAdapter,BaseAdapter。其中ArrayAdapter只能展示一行字TextView。SimpleAdapter可以自定义出各种效果。SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合。BaseAdapter具有最好的扩展性。
绘制ListView时先调用getCount()获得需要绘制的数目,再调用getView()逐行绘制,直到所有条目绘制完毕为止。注意getView函数返回的是一个实例化并设置各个组件及其数据内容的View(如果是一个简单的显示则是View,如果是一个自定义的里面包含很多控件的时候它其实是一个ViewGroup)。
设想只绘制9个条目,那么getView()返回9个View实例肯定没有问题,假如需要绘制1000个条目,那么new1000个View的实例肯定会出现OOM,这个时候就需要使用convertView,看下它的实现原理图:
从上图可以看出当“Item1”被拖动出手机界面后,会被放在Recycler回收器里,这个时候Item1就是一个convertView的实例。所以convertView实际上就是使用过后废弃的View缓存。当屏幕上只有Item1到Item7时convertView是NULL,只有当Item1超出屏幕废弃不用时convertView才有值。
二、优化
了解了原理,那么ListView性能优化总结如下:
1. 使用Google推荐的ViewHolder , setTag,getTag 必不可少,因为setTag()保存控件的引用,不用每次都调用findviewById(...)
2. 使用convertView缓存机制
3. 如果需要使用Context,尽量使用生命周期较长的Application Context,避免OOM
4. 尽量避免在Adapter中使用线程,因为线程的生命周期不可控,容易OOM
5. 根据type去new布局,其中有连个ViewHolder类
6. 多处理自定义Item里面的图片,
(1)不要拿到文件路径就去decodeFile(),应该先用Option去保存图片信息并进行缩放,另外不要加载图片到内存中去
(2)在ListView中取图片时不要直接拿路径去取图片,而是以WeakReference(比如使用WeakReference<Context> mContextRef )、SoftReference、WeakHashMap等的来存储图片信息,是图片信息不是图片哦
(3)在getView中做图片转换时,产生的中间变量一定及时释放
三、代码
目前能想到的就上面这6个方法,希望以后还能有补充。下面给出代码:
首先看一下程序运行后的图片:
代码结构:
XML:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ListView android:id="@+id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" > </ListView> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/myText1" android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="30dip" android:textColor="@drawable/darkgray" android:textSize="20sp" > </TextView> <TextView android:id="@+id/myText2" android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="30dip" android:textColor="@drawable/white" android:textSize="14sp" > </TextView> </LinearLayout>
Activity:
public class GetSIMinfo extends Activity { private TelephonyManager telMgr; //Adapter中的数据来源,取得名称和数值的泛型数组 private List<String> item=new ArrayList<String>(); private List<String> value=new ArrayList<String>(); private ListView listview; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); listview=(ListView)findViewById(R.id.list); telMgr = (TelephonyManager)getSystemService(TELEPHONY_SERVICE); //下面是动态加载Adapter的数据 /* 取得SIM卡状态 */ item.add(getResources().getText(0, "SIM卡状态").toString()); value.add("良好"); /* 取得SIM卡卡号 */ item.add(getResources().getText(0, "SIM卡卡号").toString()); value.add(telMgr.getSimSerialNumber()); /* 取得SIM卡供货商代码 */ item.add(getResources().getText(0, "SIM卡供应商代号").toString()); value.add(telMgr.getSimOperator()); /* 取得SIM卡供货商名称 */ item.add(getResources().getText(0, "SIM卡供应商名称").toString()); value.add(telMgr.getSimOperatorName()); /* 取得SIM卡国别 */ item.add(getResources().getText(0, "SIM卡国别").toString()); value.add(telMgr.getSimCountryIso()); /* 使用自定义的MyAdapter来将数据传入Activity */ MyAdapter myAdapter=new MyAdapter(this,item,value); listview.setAdapter(myAdapter); }
import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class MyAdapter extends BaseAdapter { private List<String> items; private List<String> values; /** * MyAdapter的构造器,传入三个参数 * * @param context * @param item * @param value */ public MyAdapter(Context context, List<String> item, List<String> value) { items = item; values = value; } /** * 因继承BaseAdapter,需覆盖以下方法 * * @return */ @Override public int getCount(){ return items.size(); } @Override public Object getItem(int position){ return items.get(position); } @Override public long getItemId(int position){ return position; } /** * setTag()会把View与ViewHolder绑定,形成一一对应的关系,拖动listview的时候会重新绘制每一条item * 但是那些已经取得绑定的View,会调用getTag()方法,不需要再通过findViewById去绑定,提高了效率。 */ @Override public View getView(int position, View convertView, ViewGroup par){ ViewHolder holder; if (convertView == null) { /** * 通过inflate找到布局 然后findViewById 设置各个显示的item * LayoutInflater flater = LayoutInflater.from(this); * View view = flater.inflate(R.layout.example, null); */ LayoutInflater mInflater = LayoutInflater.from(context); convertView = mInflater.inflate(R.layout.row_layout, null);//通过inflate()new出一个新的view实例 holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(R.id.myText1); holder.text2 = (TextView) convertView.findViewById(R.id.myText2); convertView.setTag(holder);//用setTag()给不同的convertView添加标识,避免重复绘制 } else { holder = (ViewHolder) convertView.getTag(); } /* 设置要显示的信息 */ holder.text1.setText(items.get(position).toString()); holder.text2.setText(values.get(position).toString()); return convertView; } private static class ViewHolder1 { TextView text1; TextView text2; } /*这里定义两个ViewHolder,目的是可以根据不同的Item布局来使用另一个ViewHolder private static class ViewHolder2 { TmageVIew image; TextView text; } */ }
在这段代码中有几点还是需要特别提出来:
1.方法public int getCount()是用来说明需要绘制的条目数量
2.方法public View getView(int position, View convertView, ViewGroup parent)用来逐条绘制,也就是说每绘制一个条目就调用一次这个方法;
3.convertView.setTag(viewHolder); // 通过ViewHolder保存控件的引用,不用每次都调用findviewById(...)
View中的setTag(Onbect)表示给View添加一个格外的数据,以后可以用getTag()将这个数据取出来。
可以用在多个Button添加一个监听器,每个Button都设置不同的setTag。这个监听器就通过getTag来分辨是哪个Button 被按下。
示例代码如下:
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button1 = (Button) findViewById(R.id.Button01); Button button2 = (Button) findViewById(R.id.Button02); MyListener listener = new MyListener(); button1.setTag(1); button1.setOnClickListener(listener); button2.setTag(2); button2.setOnClickListener(listener); } public class MyListener implements View.OnClickListener { @Override public void onClick(View v) { int tag = (Integer) v.getTag(); switch (tag) { case 1: System.out.println("button1 click"); break; case 2: System.out.println("button2 click"); break; } } } }
以上就是性能优化的主要内容。
四、其他
最后加上一段项目中其他风格的动态改变Adapter数据的代码用作参考:
Activity:
public class MainActivity extends Activity{ FirendAdapter mFirendAdapter = new FirendAdapter(this); private ListView mListView = null; @Override protected void onCreate(Bundle savedInstanceState){ // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.friend_main); initView(); } private void initView(){ // TODO Auto-generated method stub mListView = (ListView) findViewById(R.id.mlistview); mListView.setAdapter(mFirendAdapter); } @Override protected void onResume(){ // TODO Auto-generated method stub super.onResume(); //Class FirendItem存放的是每条数据的set() get()方法 ArrayList<FirendItem> mainFirendList = new ArrayList<FirendItem>(); FirendItem mainFirendItem = new FirendItem(); Bitmap defaultBit = BitmapFactory.decodeResource(this.getResources(), R.drawable.icon); mainFirendItem.setPhoto(defaultBit); mainFirendItem.setName("Bob"); mainFirendList.add(mainFirendItem); mainFirendItem.setPhoto(defaultBit); mainFirendItem.setName("Alice"); mainFirendList.add(mainFirendItem); //把数据传递给DiaryAdapter适配器 mFirendAdapter.setIsChattingorFirend(true); mFirendAdapter.setFirendList(mainFirendList); mFirendAdapter.notifyDataSetChanged();//通知刷新适配器 } }
Adapter:
public class FirendAdapter extends BaseAdapter { private Context mContext = null; //用于存放每条记录 FirendItem mFirendItem = new FirendItem(); ArrayList<FirendItem> firendList = new ArrayList<FirendItem>(); private Boolean isChattingorFirend = false;//true表示加为好友界面,默认为聊天界面 private int curPosition = -1; public FirendAdapter(Context context){ mContext = context; } @Override public int getCount(){ return firendList.size(); } @Override public Object getItem(int position){ return firendList.get(position); } @Override public long getItemId(int position){ return position; } public ArrayList<FirendItem> getFirendList() { return firendList; } public void setFirendList(ArrayList<FirendItem> firendList) { this.firendList = firendList; } public Boolean getIsChattingorFirend() { return isChattingorFirend; } public void setIsChattingorFirend(Boolean isChattingorFirend) { this.isChattingorFirend = isChattingorFirend; } @Override public View getView(int position, View convertView, ViewGroup parent){ // TODO Auto-generated method stub ViewHolder holder = null; if (isChattingorFirend){//加为好友界面 if (convertView == null){ holder = new ViewHolder(); convertView = LayoutInflater.from(mContext).inflate( R.layout.firend_join_item, parent, false); holder.firendPhoto = (ImageView) convertView .findViewById(R.id.firend_join_headphoto); holder.firendName = (TextView) convertView .findViewById(R.id.firend_join_name); holder.firendButton = (Button) convertView .findViewById(R.id.firend_join_button); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } //给每条记录添加数据源 holder.firendPhoto.setImageBitmap(firendList.get(position).getPhoto()); holder.firendName.setText(firendList.get(position).getName()); } else { //聊天界面 if (convertView == null){ holder = new ViewHolder(); convertView = LayoutInflater.from(mContext).inflate( R.layout.firend_chat_item, parent, false); holder.firendPhoto = (ImageView) convertView .findViewById(R.id.firend_chat_headphoto); holder.firendName = (TextView) convertView .findViewById(R.id.firend_chat_name); holder.firendSign = (TextView) convertView .findViewById(R.id.firend_chat_sign); holder.firendTime = (TextView) convertView .findViewById(R.id.firend_chat_time); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.firendPhoto.setImageBitmap(firendList.get(position).getPhoto()); holder.firendName.setText(firendList.get(position).getName()); holder.firendSign.setText(firendList.get(position).getSignText()); holder.firendTime.setText(firendList.get(position).getDate()); } return convertView; } private static class ViewHolder { // Bitmap firendPhoto; ImageView firendPhoto; TextView firendName; TextView firendSign; TextView firendTime; Button firendButton; } }我们在这里才用set方法来传入了具体的数据.