AdapterView性能优化(ListView,GridView,Gallery,Spinner)

在进行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个方法,希望以后还能有补充。下面给出代码:

首先看一下程序运行后的图片:

AdapterView性能优化(ListView,GridView,Gallery,Spinner)_第1张图片


代码结构:



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);
}



Adapter:

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方法来传入了具体的数据.

你可能感兴趣的:(AdapterView性能优化(ListView,GridView,Gallery,Spinner))