http://my.oschina.net/smalant/blog/50383
最近有需求需要在listView中载入不同的listItem布局,开始没有使用convertView,加载了多个item后导致了内存泄露,所以回来研究convertView在多个listItem布局时的
最近有需求需要在listView中载入不同的listItem布局,开始没有使用convertView,加载了多个item后导致了内存泄露,所以回来研究convertView在多个listItem布局时的缓存及应用,并且和大家分享
构造Adapter时,没有使用缓存的 convertView,导致内存泄露
示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}
描述:
以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:
public View getView(int position, View convertView, ViewGroup parent){ }
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。
修正示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
...
} else {
view = new Xxx(...);
...
}
return view;
}
上述代码很好的解决了内存泄露的问题,使用convertView回收一些布局供下面重构是使用。
但是如果出现如下图的需求,convertView就不太好用了,convertView在Item为单一的布局时,能够回收并重用,但是多个Item布局时,convertView的回收和重用会出现问题。
Listview中有3种Item布局,即使convertView缓存了一些布局,但是在重构时,根本不知道怎么样去让convertView返回你所需要的布局,这时你需要让adapter知道我当前有哪些布局,我重构Item时的布局选取规则,好让convertView能返回你需要的布局
需要重写一下两个函数
@Override
public int getItemViewType(int position) {}
官网解释如下,不解释了
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)]getView(int, View, ViewGroup)for the specified item.
Parameters
position | The position of the item within the adapter's data set whose view type we want. |
Returns
- An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
@Override
public int getViewTypeCount() {}
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup)for the specified item.
Parameters
position | The position of the item within the adapter's data set whose view type we want. |
Returns
- An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
上述两个函数的作用这如它的名字,得到Item的样式,得到所有的样式数量
下面直接上代码,就是上图的实现代码:
001 | package com.bestv.listViewTest; |
002 | import java.util.ArrayList; |
003 | import android.app.Activity; |
004 | import android.content.Context; |
005 | import android.os.Bundle; |
006 | import android.util.Log; |
007 | import android.view.LayoutInflater; |
008 | import android.view.View; |
009 | import android.view.ViewGroup; |
010 | import android.widget.BaseAdapter; |
011 | import android.widget.CheckBox; |
012 | import android.widget.ImageView; |
013 | import android.widget.LinearLayout; |
014 | import android.widget.ListView; |
015 | import android.widget.TextView; |
017 | public class listViewTest extends Activity { |
019 | /** Called when the activity is first created. */ |
021 | MyAdapter listAdapter; |
022 | ArrayList<String> listString; |
025 | public void onCreate(Bundle savedInstanceState) { |
026 | super .onCreate(savedInstanceState); |
027 | setContentView(R.layout.main); |
028 | listView = (ListView) this .findViewById(R.id.listview); |
029 | listString = new ArrayList<String>(); |
031 | for ( int i = 0 ; i < 100 ; i++) |
035 | listString.add(Integer.toString(i)); |
039 | listAdapter = new MyAdapter( this ); |
041 | listView.setAdapter(listAdapter); |
047 | class MyAdapter extends BaseAdapter{ |
053 | LinearLayout linearLayout = null ; |
055 | LayoutInflater inflater; |
059 | final int VIEW_TYPE = 3 ; |
061 | final int TYPE_1 = 0 ; |
063 | final int TYPE_2 = 1 ; |
065 | final int TYPE_3 = 2 ; |
069 | public MyAdapter(Context context) { |
071 | // TODO Auto-generated constructor stub |
075 | inflater = LayoutInflater.from(mContext); |
083 | public int getCount() { |
085 | // TODO Auto-generated method stub |
087 | return listString.size(); |
093 | //每个convert view都会调用此方法,获得当前所需要的view样式 |
097 | public int getItemViewType( int position) { |
099 | // TODO Auto-generated method stub |
127 | public int getViewTypeCount() { |
129 | // TODO Auto-generated method stub |
139 | public Object getItem( int arg0) { |
141 | // TODO Auto-generated method stub |
143 | return listString.get(arg0); |
151 | public long getItemId( int position) { |
153 | // TODO Auto-generated method stub |
163 | public View getView( int position, View convertView, ViewGroup parent) { |
165 | // TODO Auto-generated method stub |
167 | viewHolder1 holder1 = null ; |
169 | viewHolder2 holder2 = null ; |
171 | viewHolder3 holder3 = null ; |
173 | int type = getItemViewType(position); |
179 | //无convertView,需要new出各个控件 |
181 | if (convertView == null ) |
185 | Log.e( "convertView = " , " NULL" ); |
197 | convertView = inflater.inflate(R.layout.listitem1, parent, false ); |
199 | holder1 = new viewHolder1(); |
201 | holder1.textView = (TextView)convertView.findViewById(R.id.textview1); |
203 | holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox); |
205 | Log.e( "convertView = " , "NULL TYPE_1" ); |
207 | convertView.setTag(holder1); |
213 | convertView = inflater.inflate(R.layout.listitem2, parent, false ); |
215 | holder2 = new viewHolder2(); |
217 | holder2.textView = (TextView)convertView.findViewById(R.id.textview2); |
219 | Log.e( "convertView = " , "NULL TYPE_2" ); |
221 | convertView.setTag(holder2); |
227 | convertView = inflater.inflate(R.layout.listitem3, parent, false ); |
229 | holder3 = new viewHolder3(); |
231 | holder3.textView = (TextView)convertView.findViewById(R.id.textview3); |
233 | holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview); |
235 | Log.e( "convertView = " , "NULL TYPE_3" ); |
237 | convertView.setTag(holder3); |
249 | //有convertView,按样式,取得不用的布局 |
257 | holder1 = (viewHolder1) convertView.getTag(); |
259 | Log.e( "convertView !!!!!!= " , "NULL TYPE_1" ); |
265 | holder2 = (viewHolder2) convertView.getTag(); |
267 | Log.e( "convertView !!!!!!= " , "NULL TYPE_2" ); |
273 | holder3 = (viewHolder3) convertView.getTag(); |
275 | Log.e( "convertView !!!!!!= " , "NULL TYPE_3" ); |
293 | holder1.textView.setText(Integer.toString(position)); |
295 | holder1.checkBox.setChecked( true ); |
301 | holder2.textView.setText(Integer.toString(position)); |
307 | holder3.textView.setText(Integer.toString(position)); |
309 | holder3.imageView.setBackgroundResource(R.drawable.icon); |
在getView()中需要将不同布局进行缓存和适配,系统在判断是否有convertView时,会自动去调用getItemViewType (int position) ,查看是否已经有缓存的该类型的布局,从而进入if(convertView == null)和else{}的判断。期间需要做的是convertView.setTag(holder3),以便在convertView重用时可以直接拿到该布局的控件,holder3 = (viewHolder3) convertView.getTag()。到这一步,convertView的回收和重用就已经写好了,接下来只需要对你的不同的控件进行设置就行了。
缓存及应用,并且和大家分享
构造Adapter时,没有使用缓存的 convertView,导致内存泄露
示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}
描述:
以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:
public View getView(int position, View convertView, ViewGroup parent){ }
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。
修正示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
...
} else {
view = new Xxx(...);
...
}
return view;
}
上述代码很好的解决了内存泄露的问题,使用convertView回收一些布局供下面重构是使用。
但是如果出现如下图的需求,convertView就不太好用了,convertView在Item为单一的布局时,能够回收并重用,但是多个Item布局时,convertView的回收和重用会出现问题。
Listview中有3种Item布局,即使convertView缓存了一些布局,但是在重构时,根本不知道怎么样去让convertView返回你所需要的布局,这时你需要让adapter知道我当前有哪些布局,我重构Item时的布局选取规则,好让convertView能返回你需要的布局
需要重写一下两个函数
@Override
public int getItemViewType(int position) {}
官网解释如下,不解释了
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)]
getView(int, View, ViewGroup)
for the specified item.
Parameters
position | The position of the item within the adapter's data set whose view type we want. |
Returns
- An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
@Override
public int getViewTypeCount() {}
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)
getView(int, View, ViewGroup)
for the specified item.
Parameters
position | The position of the item within the adapter's data set whose view type we want. |
Returns
- An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
上述两个函数的作用这如它的名字,得到Item的样式,得到所有的样式数量
下面直接上代码,就是上图的实现代码:
001 | package com.bestv.listViewTest; |
002 | import java.util.ArrayList; |
003 | import android.app.Activity; |
004 | import android.content.Context; |
005 | import android.os.Bundle; |
006 | import android.util.Log; |
007 | import android.view.LayoutInflater; |
008 | import android.view.View; |
009 | import android.view.ViewGroup; |
010 | import android.widget.BaseAdapter; |
011 | import android.widget.CheckBox; |
012 | import android.widget.ImageView; |
013 | import android.widget.LinearLayout; |
014 | import android.widget.ListView; |
015 | import android.widget.TextView; |
017 | public class listViewTest extends Activity { |
019 | /** Called when the activity is first created. */ |
021 | MyAdapter listAdapter; |
022 | ArrayList<String> listString; |
025 | public void onCreate(Bundle savedInstanceState) { |
026 | super .onCreate(savedInstanceState); |
027 | setContentView(R.layout.main); |
028 | listView = (ListView) this .findViewById(R.id.listview); |
029 | listString = new ArrayList<String>(); |
031 | for ( int i = 0 ; i < 100 ; i++) |
035 | listString.add(Integer.toString(i)); |
039 | listAdapter = new MyAdapter( this ); |
041 | listView.setAdapter(listAdapter); |
047 | class MyAdapter extends BaseAdapter{ |
053 | LinearLayout linearLayout = null ; |
055 | LayoutInflater inflater; |
059 | final int VIEW_TYPE = 3 ; |
061 | final int TYPE_1 = 0 ; |
063 | final int TYPE_2 = 1 ; |
065 | final int TYPE_3 = 2 ; |
069 | public MyAdapter(Context context) { |
071 | // TODO Auto-generated constructor stub |
075 | inflater = LayoutInflater.from(mContext); |
083 | public int getCount() { |
085 | // TODO Auto-generated method stub |
087 | return listString.size(); |
093 | //每个convert view都会调用此方法,获得当前所需要的view样式 |
097 | public int getItemViewType( int position) { |
099 | // TODO Auto-generated method stub |
127 | public int getViewTypeCount() { |
129 | // TODO Auto-generated method stub |
139 | public Object getItem( int arg0) { |
141 | // TODO Auto-generated method stub |
143 | return listString.get(arg0); |
151 | public long getItemId( int position) { |
153 | // TODO Auto-generated method stub |
163 | public View getView( int position, View convertView, ViewGroup parent) { |
165 | // TODO Auto-generated method stub |
167 | viewHolder1 holder1 = null ; |
169 | viewHolder2 holder2 = null ; |
171 | viewHolder3 holder3 = null ; |
173 | int type = getItemViewType(position); |
179 | //无convertView,需要new出各个控件 |
181 | if (convertView == null ) |
185 | Log.e( "convertView = " , " NULL" ); |
197 | convertView = inflater.inflate(R.layout.listitem1, parent, false ); |
199 | holder1 = new viewHolder1(); |
201 | holder1.textView = (TextView)convertView.findViewById(R.id.textview1); |
203 | holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox); |
205 | Log.e( "convertView = " , "NULL TYPE_1" ); |
207 | convertView.setTag(holder1); |
213 | convertView = inflater.inflate(R.layout.listitem2, parent, false ); |
215 | holder2 = new viewHolder2(); |
217 | holder2.textView = (TextView)convertView.findViewById(R.id.textview2); |
219 | Log.e( "convertView = " , "NULL TYPE_2" ); |
221 | convertView.setTag(holder2); |
227 | convertView = inflater.inflate(R.layout.listitem3, parent, false ); |
229 | holder3 = new viewHolder3(); |
231 | holder3.textView = (TextView)convertView.findViewById(R.id.textview3); |
233 | holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview); |
235 | Log.e( "convertView = " , "NULL TYPE_3" ); |
237 | convertView.setTag(holder3); |
249 | //有convertView,按样式,取得不用的布局 |
257 | holder1 = (viewHolder1) convertView.getTag(); |
259 | Log.e( "convertView !!!!!!= " , "NULL TYPE_1" ); |
265 | holder2 = (viewHolder2) convertView.getTag(); |
267 | Log.e( "convertView !!!!!!= " , "NULL TYPE_2" ); |
273 | holder3 = (viewHolder3) convertView.getTag(); |
275 | Log.e( "convertView !!!!!!= " , "NULL TYPE_3" ); |
293 | holder1.textView.setText(Integer.toString(position)); |
295 | holder1.checkBox.setChecked( true ); |
301 | holder2.textView.setText(Integer.toString(position)); |
307 | holder3.textView.setText(Integer.toString(position)); |
309 | holder3.imageView.setBackgroundResource(R.drawable.icon); |
在getView()中需要将不同布局进行缓存和适配,系统在判断是否有convertView时,会自动去调用getItemViewType (int position) ,查看是否已经有缓存的该类型的布局,从而进入if(convertView == null)和else{}的判断。期间需要做的是convertView.setTag(holder3),以便在convertView重用时可以直接拿到该布局的控件,holder3 = (viewHolder3) convertView.getTag()。到这一步,convertView的回收和重用就已经写好了,接下来只需要对你的不同的控件进行设置就行了。