首先来了解一下ListView的工作原理(可参见http://mobile.51cto.com/abased-410889.htm),如图:
1、如果你有几千几万甚至更多的选项(item)时,其中只有可见的项目存在内存(内存内存哦,说的优化就是说在内存中的优化!!!)中,其他的在Recycler中
2、ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的
3、当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图
一、复用convertView,减少findViewById的次数
1、优化一:复用convertView
Android系统本身为我们考虑了ListView的优化问题,在复写的Adapter的类中,比较重要的两个方法是getCount()和getView()。界面上有多少个条显示,就会调用多少次的getView()方法;因此如果在每次调用的时候,如果不进行优化,每次都会使用View.inflate(….)的方法,都要将xml文件解析,并显示到界面上,这是非常消耗资源的:因为有新的内容产生就会有旧的内容销毁,所以,可以复用旧的内容。
优化:
在getView()方法中,系统就为我们提供了一个复用view的历史缓存对象convertView,当显示第一屏的时候,每一个item都会新创建一个view对象,这些view都是可以被复用的;如果每次显示一个view都要创建一个,是非常耗费内存的;所以为了节约内存,可以在convertView不为null的时候,对其进行复用
2、优化二:缓存item条目的引用——ViewHolder
findViewById()这个方法是比较耗性能的操作,因为这个方法要找到指定的布局文件,进行不断地解析每个节点:从最顶端的节点进行一层一层的解析查询,找到后在一层一层的返回,如果在左边没找到,就会接着解析右边,并进行相应的查询,直到找到位置(如图)。因此可以对findViewById进行优化处理,需要注意的是:
》》》》特点:xml文件被解析的时候,只要被创建出来了,其孩子的id就不会改变了。根据这个特点,可以将孩子id存入到指定的集合中,每次就可以直接取出集合中对应的元素就可以了。
优化:
在创建view对象的时候,减少布局文件转化成view对象的次数;即在创建view对象的时候,把所有孩子全部找到,并把孩子的引用给存起来
①定义存储控件引用的类ViewHolder
这里的ViewHolder类需要不需要定义成static,根据实际情况而定,如果item不是很多的话,可以使用,这样在初始化的时候,只加载一次,可以稍微得到一些优化
不过,如果item过多的话,建议不要使用。因为static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(比如Context的情况最多),这时就要尽量避免使用了。
class ViewHolder{
//定义item中相应的控件
}
②创建自定义的类:ViewHolder holder = null;
③将子view添加到holder中:
在创建新的listView的时候,创建新的ViewHolder,把所有孩子全部找到,并把孩子的引用给存起来
通过view.setTag(holder)将引用设置到view中
通过holder,将孩子view设置到此holder中,从而减少以后查询的次数
④在复用listView中的条目的时候,通过view.getTag(),将view对象转化为holder,即转化成相应的引用,方便在下次使用的时候存入集合。
通过view.getTag(holder)获取引用(需要强转)
示例代码:
public class ActivityDemo extends Activity {
private ListView listview1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listview1 = (ListView) findViewById(R.id.listview1);
MyAdapter adapter = new MyAdapter();
listview1.setAdapter(adapter);
}
private class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return 40;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if(convertView!=null && convertView instanceof RelativeLayout){ //注意:这里不一定用RelativeLayout,根据XML文件中的根节点来确定
holder = (ViewHolder) convertView.getTag();
}else{
//1、复用历史缓存view对象,检索布局问转化成view对象的次数
convertView = View.inflate(ActivityDemo.this, R.layout.item, null);
//2、在创建view对象的时候,把所有的子view找到,把子view的引用存起来
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
convertView.setTag(holder);
/* 实现存储子view引用的另一种方式:
convertView.setTag(holder.ivIcon);
convertView.setTag(holder.tvContent); */
}
//直接复用系统提供的历史缓存对象convertView
return convertView;
}
}
class ViewHolder{
public ImageView ivIcon;
public TextView tvContent;
}
}
二、ListView中数据的分批及分页加载:
需求:
ListView有一万条数据,如何显示;如果将十万条数据加载到内存,很消耗内存
解决办法:
优化查询的数据:先获取几条数据显示到界面上
进行分批处理---à优化了用户体验
进行分页处理---à优化了内存空间
说明:
一般数据都是从数据库中获取的,实现分批(分页)加载数据,就需要在对应的DAO中有相应的分批(分页)获取数据的方法,如findPartDatas ()
1、准备数据:
在dao中添加分批加载数据的方法:findPartDatas ()
在适配数据的时候,先加载第一批的数据,需要加载第二批的时候,设置监听检测何时加载第二批
2、设置ListView的滚动监听器:setOnScrollListener(new OnScrollListener{….})
①、在监听器中有两个方法:滚动状态发生变化的方法(onScrollStateChanged)和listView被滚动时调用的方法(onScroll)
②、在滚动状态发生改变的方法中,有三种状态:
手指按下移动的状态: SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动
惯性滚动(滑翔(flgin)状态): SCROLL_STATE_FLING: // 滑翔
静止状态: SCROLL_STATE_IDLE: // 静止
3、对不同的状态进行处理:
分批加载数据,只关心静止状态:关心最后一个可见的条目,如果最后一个可见条目就是数据适配器(集合)里的最后一个,此时可加载更多的数据。在每次加载的时候,计算出滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户无更多数据了。
示例代码:(详细代码请参见【6.3-ListView数据的分批加载.doc】)
// 给listview注册一个滚动的监听器.
lv_call_sms_safe.setOnScrollListener(new OnScrollListener() {
// 当滚动状体发生变化的时候调用的方法
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_FLING: // 滑翔
break;
case SCROLL_STATE_IDLE: // 静止
// 在静止状态下 关心最后一个可见的条目 如果最后一个可见条目就是 数据适配器里面的最后一个 , 加载更多数据.
int position = lv_call_sms_safe.getLastVisiblePosition(); // 位置从0开始
int size = blackNumbers.size();// 从1开始的.
if (position == (size - 1)) {
Log.i(TAG, "拖动到了最后一个条目,加载更多数据");
startIndex += maxNumber;
if(startIndex>=totalCount){
Toast.makeText(getApplicationContext(), "没有更多数据了..", 0).show();
return;
}
fillData();
break;
}
break;
case SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动
break;
}
}
// 当listview被滚动的时候 调用的方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
/**
* 填充数据
*/
private void fillData() {
// 通知用户一下正在获取数据
ll_loading.setVisibility(View.VISIBLE);
new Thread() {
public void run() {
// 获取全部的黑名单号码
if (blackNumbers != null) {
blackNumbers.addAll(dao.findPartBlackNumbers(startIndex,maxNumber));
} else {
blackNumbers = dao.findPartBlackNumbers(startIndex,maxNumber);
}
handler.sendEmptyMessage(0);
// lv_call_sms_safe.setAdapter(new CallSmsSafeAdapter());
};
}.start();
}