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