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

来源:互联网 发布:禁止外网访问3306端口 编辑:程序博客网 时间:2024/05/05 15:43

在进行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);}



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;@Overrideprotected void onCreate(Bundle savedInstanceState){// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.friend_main);initView();}private void initView(){// TODO Auto-generated method stubmListView = (ListView) findViewById(R.id.mlistview);mListView.setAdapter(mFirendAdapter);}@Overrideprotected void onResume(){// TODO Auto-generated method stubsuper.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;}@Overridepublic int getCount(){return firendList.size();}@Overridepublic Object getItem(int position){return firendList.get(position);}@Overridepublic 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;}@Overridepublic View getView(int position, View convertView, ViewGroup parent){// TODO Auto-generated method stubViewHolder 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方法来传入了具体的数据.

1 0
原创粉丝点击