listview作为Android中最常用的控件之一,可以以条目的形式展示大量的数据列表,对于每一个item,均要求adapter适配器的getiView()方法返回一个view,因此listview的实现,离不开Adapter。
如果不对adapter的getView()方法进行优化的话,会造成内存被占用,这样如果数据量比较大的话,很容易造成手机内存被消耗,这样app的使用体验就很差。
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = null;
- System.out.println("before: "+list.get(position)+"------- "+convertView);
- view = inflater.inflate(R.layout.item, null);
- System.out.println("after: "+list.get(position)+"------- "+view);
- TextView textView = (TextView) view.findViewById(R.id.textView);
- textView.setText(list.get(position));
- return view;
- }
一下是一种没有优化的方式,也是最初的使用,接下来我们来看下控制面板的打印结果;
可以发现初始状态有10个item显示在界面上,并且他们的before view均是null的,调用inflate方法之后生成了新的view,因此after view非空了;
由于我们目前的数据没有那么多,但是一旦我们有大量数据需要显示的话,直接就会报内存的 。
通过上面我们会发现一点,在调用getView方法的时候,他的第二个参数可能不会是null的,原因就是RecycleBin缓存帮我们暂存了那些划出屏幕的view,所以我们在convertView非空的情况下我们也没什么必要重新调用inflate方法加载布局了,因为这个方法毕竟也是要解析xml文件的,至少是要花时间的,直接使用从缓存中取出的view即可啦
接下裏我們來看看优化的代码;
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = null;
- System.out.println("before: "+list.get(position)+"------- "+convertView);
- if(convertView == null)
- {
- view = inflater.inflate(R.layout.item, null);
- }else
- view = convertView;
- TextView textView = (TextView) view.findViewById(R.id.textView);
- textView.setText(list.get(position));
- return view;
- }
接着我们滑动屏幕,查看输出:
注意红色部分,发现没经过11个item,都会复用之前的view,这也就是说我们的RecycleBin缓存中将只有11个view了,不像前面那样有50个view,这在很大程度上节约了内存,想想如果有上千万条数据需要显示,每个数据条目都有一个view在RecycleBin中是一件多么可怕的事情,进行了convertView是否为null的判断之后,将只会缓存一屏幕的view,当然有可能会多那么几个吧;
上面我们通过判断convertView是否为空对ListView进行了优化,接下来我们看看getView方法,里面在获取TextView的时候,我们使用了findViewById方法,这个方法是与IO有关的操作,想必也会影响性能吧,他只要的目的是获得某一个view的布局罢了,我们如果有了view的话,其实只需要第一次将该view和其布局绑定到一起就可以了,没必要每次都为view设置布局了,这也就是使用setTag的目的了;
我们来总结一下:
1.通过复用的view的方式来充分利用Android系统本身自带的RecycleBin缓存机制,能够保证即使有再多的item实际中也仅仅会有有限多个item,大大节省内存;
2.使用setTag方式,将view与其对应的控件绑定起来,避免了每次得到view以后都需要通过findViewById的方式来获取控件;
3.对于有多种布局的ListView来说,我们可以通过getViewTypeCount()获得布局的种类,通过getItemViewType(int)获得当前位置上的布局到底是属于哪一类;
另外,使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的 方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView 设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需 要为其显式设置属性(Textview的文本为空也需要设置 setText(""),背景透明也需要 设置),
下面是一个例子:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder myViews;
if (convertView == null) {
myViews = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
myViews.mUsername = (TextView)convertView.findViewById(R.id.username);
convertView.setTag(myViews);
} else {
myViews = (ViewHolder)convertView.getTag();
}
Info p = infoList.get(position);
String dn = p.getDisplayName;
myViews.mUsername.setText(StringUtils.isEmpty(dn) ? "" : dn);
return convertView;
}
static class ViewHolder {
private TextView mUsername;
}
这样使用之后,你会发现你的app的性能能提高很多,赶快学习下吧