ListView简介
ListView 是一个显示一列可滚动项目的视图组。 系统使用 Adapter 自动将列表项目插入列表,适配器从来源(例如数组或数据库查询)提取内容,并将每个项目结果转换为视图放置到列表中。 ——Android Developers
最初的写法
class DemoAdapter extends BaseAdapter{
ArrayList<AppInfo> mList = new ArrayList<AppInfo>();
public DemoAdapter(ArrayList<AppInfo> list){
mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public AppInfo getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(MainActivity.this, R.layout.list_item, null);
TextView title = (TextView) view.findViewById(R.id.tv_title);
ImageView icon = (ImageView) view.findViewById(R.id.iv_icon);
title.setText(getItem(position).getTitle());
icon.setImageResource(R.drawable.ic_launcher);
return view;
}
}
以上代码的getView()方法在每个子item被滚到屏幕内(即可见)的时候,都会调用一次!如果List的大小比较大的时候(例如为100),在屏幕内滚动ListView,极容易发生OOM。Google的工程师们早已考虑到了这些,Android中有个叫做Recycler的构件,下图是他的工作原理图:
- 如上图所示,假设当前屏幕只能容纳ListView下type1类型视图下的8个子item,此时存在于Rceycler的convertView为空。
- 当item1滚出屏幕,并且一个新的项目item8从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。
并且你要清楚在Android中,填充视图的inflate()操作是消耗GPU的ALU(算术逻辑单元)资源的,属于耗时的I/O操作。此时我们再来看看getView()的参数,发现恰好第二个参数为convertView,查看getView()的说明,对这个参数的解释就是对旧视图的重用,这个参数用于将之前加载好的布局进行缓存。那么在一定程度上可以减少inflate()操作,我们在下面可以修改一下代码。
改进的写法Ⅰ
class DemoAdapter extends BaseAdapter{
ArrayList<AppInfo> mList = new ArrayList<AppInfo>();
public DemoAdapter(ArrayList<AppInfo> list){
mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public AppInfo getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
//判断条目是否有缓存
if(convertView == null){
//把布局文件填充成一个View对象,第三个参数一般为空
view = View.inflate(MainActivity.this, R.layout.list_item, null);
}
else{
view = convertView;
}
TextView title = (TextView) view.findViewById(R.id.tv_title);
ImageView icon = (ImageView) view.findViewById(R.id.iv_icon);
title.setText(getItem(position).getTitle());
icon.setImageResource(R.drawable.ic_launcher);
return view;
}
}
我们在getView()方法中进行了判断,如果convertView 为空,则使用LayoutInflater 去加载布局,如果不为空则直接对convertView 进行重用。这样就大大提高了ListView 的运行效率,在快速滚动的时候也可以表现出更好的性能。
不过,目前我们的这份代码还是可以继续优化的,了解过自定义View的同学都知道,findViewById()这个方法是以视图树的深度优先遍历查找的,当item中含有较复杂的布局时,频繁的回调findViewById()方法无疑会影响滑动的性能,亦属于耗时的I/O操作。
我们可以借助一个ViewHolder 来对这部分性能进行优化,并且其最好是静态内部类。静态内部类,不持有外部类的引用,避免内存泄露…
effective Java中有提到过,static class的适用场景,主要作用是为了减少大量内部类都各自持有外部类引用带来的内存开销。
非static内部类会持有外部类的引用,这样就会涉及到外部类的释放依赖内部类。如果内部类过多的引用外部类的话,这样很容易造成外部类得不到释放。
这样就有了我们的第三种写法。
改进的写法Ⅱ
class DemoAdapter extends BaseAdapter{
ArrayList<AppInfo> mList = new ArrayList<AppInfo>();
public DemoAdapter(ArrayList<AppInfo> list){
mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public AppInfo getItem(int position) {
return mList.get(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 = View.inflate(MainActivity.this, R.layout.list_item, null);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.tv_title);
holder.icon = (ImageView) convertView.findViewById(R.id.iv_icon);
convertView.setTag(holder);// 将holder与convertView进行绑定
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.title.setText(getItem(position).getTitle());
holder.icon.setImageResource(R.drawable.ic_launcher);
return convertView;
}
static class ViewHolder{
ImageView icon;
TextView title;
}
}
OK!大功告成!
本文参考的文献
1.安诺爱思考的优快云博客
2.红黑联盟:ListView深入理解:性能优化1
3.木乃猫的学习笔记的博客园
4.郭霖的《第一行代码》