在项目优化过程中,通过MAT监控发现存在一处内存泄露,反复进入某个页面,内存占用越来越大。后分析找到了泄露原因,原来是在自定义列表中,将行布局的layout文件inflate成view对象的时候,每加载一次列表就要new出一组新的view对象。因为没有对这些布局一致的view进行复用,又没法及时释放,导致了列表的行布局对象越积越多,造成内存泄露。
解决这个oom问题,首先想到了listview加载中对convertview的回收和复用的方法。于是模仿convertview的原理写了个对view对象进行回收和复用的类,此处类名为ViewRecycler,使用后有效的解决了view对象的复用,远离这个棘手的oom问题。由于项目中需要复用的view对象布局都是一样的,此方法只考虑了复用同一布局的情况。同时,项目中的列表已有获取显示行与隐藏行的相应接口,此方法仅主要从回收与复用的逻辑层面加以实现,并未涉及任何底层代码部分。
一、以下是具体的实现过程:
1.首先ViewRecycler类需要两个容器分别用来保存活动的View对象和回收可复用的View对象。一个map用来保存回收的View(无序添加),一个数组用来记录当前显示的行号以及对应的View(有序添加)。
如下:
1.
private
SparseArray<View> recycleViews;
// 废弃的view
2.
private
View[] activeViews =
new
View[
0
];
//正在使用的view
注:key为int类型的HashMap用SparseArray代替,会有更好的性能.
2.每次列表刷新或变化,就更新一次activeViews的大小。即activeViews的数组长度与当前列表的总行数一致。在刷新列表或者加载更多时,调用getCount()方法更新activeViews数组长度。
如下:
1.
// 加载完毕
2.
private
void
loadComplete()
3.
{
4.
//.....
5.
mViewRecycler.getCount(mDataList.size());
6.
}
ViewRecycler类里的getCount方法:
01.
/** 获取活动view的总数 */
02.
public
void
getCount(
int
count)
03.
{
04.
final
int
length =
this
.activeViews.length;
05.
if
(count > length)
06.
{
07.
final
View[] activeViews =
this
.activeViews;
08.
this
.activeViews = Arrays.copyOf(activeViews, count);
09.
10.
// Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]);
11.
}
12.
}
3.列表每新增显示一行,就先获取是否有可复用的View对象。先判断recycleViews是否已存有该行号对应的View,没有则获取最新回收的View。再结合setTag与getTag便可实现对回收View对象的复用了。如果recycleViews没有可复用的View,则inflate生成新的View。
如下:
01.
public
void
convertFromView(
final
ListJson listJson,
final
int
n)
02.
{
03.
ViewHolder holder =
null
;
04.
05.
// 判断是否有可重复利用的view
06.
View resycleView = mViewRecycler.getRecycleView(n);
07.
08.
if
(resycleView ==
null
)
09.
{
10.
resycleView = LayoutInflater.from(getActivity()).inflate(R.layout.list_item,
null
);
11.
12.
holder =
new
ViewHolder();
13.
//.....
14.
holder.iv_main = (ImageView) resycleView.findViewById(R.id.item_iv_main);
15.
16.
resycleView.setTag(holder);
17.
}
18.
else
19.
{
20.
holder = (ViewHolder) resycleView.getTag();
21.
}
22.
23.
//保存新增活动的View对象
24.
mViewRecycler.addActiveView(n, resycleView);
25.
26.
// 加载行布局数据
27.
holder.tv_title.setText(lison.getName());
28.
holder.tv_price.setText(
"待定"
);
29.
//.....
30.
31.
// 下载图片
32.
ImageLoadingListener listener =
new
ImageLoadingListener()
33.
{
34.
@Override
35.
public
void
onLoadingStarted(String arg0, View arg1)
36.
{
37.
}
38.
39.
@Override
40.
public
void
onLoadingComplete(String arg0, View arg1, Bitmap bitmap)
41.
{
42.
//获取view对象
43.
View bitmapView = mViewRecycler.getActiveView(n);
44.
if
(bitmapView !=
null
)
45.
{
46.
ViewHolder holder = (ViewHolder) bitmapView.getTag();
47.
if
(holder !=
null
)
48.
{
49.
// 加载图片
50.
holder.iv_main.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.bg_item));
51.
}
52.
}
53.
}
54.
55.
@Override
56.
public
void
onLoadingFailed(String arg0, View arg1, FailReason arg2)
57.
{
58.
}
59.
60.
@Override
61.
public
void
onLoadingCancelled(String arg0, View arg1)
62.
{
63.
}
64.
};
65.
imageLoader.loadImage(listJson.getStatusPic(), listener);
66.
}
ViewRecycler类里的对应方法:
(1)将行号与对应的View填充到activeViews数组里保存。添加前对行号与activeViews的长度进行校正,避免越界。
01.
/** 添加记录当前活动的view */
02.
public
void
addActiveView(
int
position, View view)
03.
{
04.
final
int
length =
this
.activeViews.length;
05.
if
(position > length -
1
)
06.
{
07.
getCount(position +
1
);
08.
}
09.
this
.activeViews[position] = view;
10.
11.
// Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews));
12.
}
(2)根据行号获取对应的View对象。
01.
/** 获取某个活动view */
02.
public
View getActiveView(
int
position)
03.
{
04.
final
int
length =
this
.activeViews.length;
05.
if
(position > length -
1
)
06.
{
07.
getCount(position +
1
);
08.
}
09.
return
activeViews[position];
10.
}
(3)获取已回收的View对象。
01.
/** 获取回收的view */
02.
View getRecycleView(
int
position)
03.
{
04.
return
retrieveFromRecycle(recycleViews, position);
05.
}
06.
/** 检索回收的view */
07.
static
View retrieveFromRecycle(SparseArray<View> recycleViews,
int
position)
08.
{
09.
int
size = recycleViews.size();
10.
if
(size >
0
)
11.
{
12.
// See if we still have a view for this position.
13.
for
(
int
i =
0
; i < size; i++)
14.
{
15.
int
fromPosition = recycleViews.keyAt(i);
16.
View view = recycleViews.get(fromPosition);
17.
if
(fromPosition == position)
18.
{
19.
recycleViews.remove(fromPosition);
20.
return
view;
21.
}
22.
}
23.
int
index = size -
1
;
24.
View r = recycleViews.valueAt(index);
25.
recycleViews.remove(recycleViews.keyAt(index));
26.
return
r;
27.
}
28.
else
29.
{
30.
return
null
;
31.
}
32.
}
4.列表每隐藏一行,将消失的行布局对应的View对象回收,添加到recycleViews容器里,同时移除activeViews里的这个View。然后再进行比较并清除recycleViews容器里的回收对象,保证回收对象总数不多于活动view容器的总长度。
如下:
01.
// 隐藏
02.
@Override
03.
public
void
onInvalidateItem(
int
id)
04.
{
05.
super
.onInvalidateItem(id);
06.
// .....
07.
08.
09.
// 回收view对象
10.
View recycleView = mViewRecycler.getActiveView(id);
11.
if
(recycleView !=
null
)
12.
{
13.
mViewRecycler.addRecycleView(id, recycleView);
14.
}
15.
16.
}
ViewRecycler类里的对应方法:
01.
/** 添加废弃的view,无序添加 */
02.
void
addRecycleView(
int
position, View scrap)
03.
{
04.
recycleViews.put(position, scrap);
05.
06.
final
int
length =
this
.activeViews.length;
07.
if
(position < length)
08.
{
09.
this
.activeViews[position] =
null
;
10.
}
11.
pruneRecycleViews();
12.
// Log.e("Recycle", "Recycle.size() = " +recycleViews.size());
13.
}
01.
/** 确保废弃的view总数不多于活动的view容器的长度(此方法可再改进为不多于当前活动的View对象数量) */
02.
private
void
pruneRecycleViews()
03.
{
04.
final
int
maxViews = activeViews.length;
05.
int
size = recycleViews.size();
06.
final
int
extras = size - maxViews;
07.
size--;
08.
for
(
int
j =
0
; j < extras; j++)
09.
{
10.
recycleViews.remove(recycleViews.keyAt(size--));
11.
}
12.
}
5.退出时,清除所有View对象及其引用。
以下方法则是根据项目需求,将所有的活动view移到recycleViews里,再整理recycleViews。
如下:
1.
@Override
2.
public
void
onDestroy()
3.
{
4.
super
.onDestroy();
5.
//销毁所有view对象
6.
mViewRecycler.recycleAllActiveViews();
7.
}
ViewRecycler类里的对应方法:
01.
/** 将所有剩余的活动view移到废弃view里 */
02.
void
recycleAllActiveViews()
03.
{
04.
final
View[] activeViews =
this
.activeViews;
05.
SparseArray<View> recycleViews =
this
.recycleViews;
06.
final
int
count = activeViews.length;
07.
for
(
int
i = count -
1
; i >=
0
; i--)
08.
{
09.
final
View victim = activeViews[i];
10.
if
(victim !=
null
)
11.
{
12.
activeViews[i] =
null
;
13.
recycleViews.put(i, victim);
14.
}
15.
}
16.
17.
pruneRecycleViews();
18.
}
二、调试结果
滑动时,请求加载行数据。每次添加一个行布局对象,当达到10行后开始执行View对象回收。每次将栈底的View回收并复用到栈顶的行布局里。如下图:
三、最后附上完整的ViewRecycler类
如下:
001.
public
class
ViewRecycler
002.
{
003.
private
SparseArray<View> recycleViews;
// 废弃的view
004.
private
View[] activeViews =
new
View[
0
];
//正在使用的view
005.
006.
public
ViewRecycler()
007.
{
008.
recycleViews =
new
SparseArray<View>();
009.
}
010.
011.
/** 获取活动view的总数 */
012.
public
void
getCount(
int
count)
013.
{
014.
final
int
length =
this
.activeViews.length;
015.
if
(count > length)
016.
{
017.
final
View[] activeViews =
this
.activeViews;
018.
this
.activeViews = Arrays.copyOf(activeViews, count);
019.
020.
// Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]);
021.
}
022.
}
023.
024.
/** 添加记录当前活动的view */
025.
public
void
addActiveView(
int
position, View view)
026.
{
027.
final
int
length =
this
.activeViews.length;
028.
if
(position > length -
1
)
029.
{
030.
getCount(position +
1
);
031.
}
032.
this
.activeViews[position] = view;
033.
034.
// Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews));
035.
}
036.
037.
/** 获取某个活动view */
038.
public
View getActiveView(
int
position)
039.
{
040.
final
int
length =
this
.activeViews.length;
041.
if
(position > length -
1
)
042.
{
043.
getCount(position +
1
);
044.
}
045.
return
activeViews[position];
046.
}
047.
048.
/** 获取废弃的view */
049.
View getRecycleView(
int
position)
050.
{
051.
return
retrieveFromRecycle(recycleViews, position);
052.
}
053.
054.
/** 检索废弃的view */
055.
static
View retrieveFromRecycle(SparseArray<View> recycleViews,
int
position)
056.
{
057.
int
size = recycleViews.size();
058.
if
(size >
0
)
059.
{
060.
// See if we still have a view for this position.
061.
for
(
int
i =
0
; i < size; i++)
062.
{
063.
int
fromPosition = recycleViews.keyAt(i);
064.
View view = recycleViews.get(fromPosition);
065.
if
(fromPosition == position)
066.
{
067.
recycleViews.remove(fromPosition);
068.
return
view;
069.
}
070.
}
071.
int
index = size -
1
;
072.
View r = recycleViews.valueAt(index);
073.
recycleViews.remove(recycleViews.keyAt(index));
074.
return
r;
075.
}
076.
else
077.
{
078.
return
null
;
079.
}
080.
}
081.
082.
/** 添加废弃的view,无序添加 */
083.
void
addRecycleView(
int
position, View scrap)
084.
{
085.
recycleViews.put(position, scrap);
086.
087.
final
int
length =
this
.activeViews.length;
088.
if
(position < length)
089.
{
090.
this
.activeViews[position] =
null
;
091.
}
092.
pruneRecycleViews();
093.
// Log.e("Recycle", "Recycle.size() = " +recycleViews.size());
094.
}
095.
096.
/** 将所有剩余的活动view移到废弃view里 */
097.
void
recycleAllActiveViews()
098.
{
099.
final
View[] activeViews =
this
.activeViews;
100.
SparseArray<View> recycleViews =
this
.recycleViews;
101.
final
int
count = activeViews.length;
102.
for
(
int
i = count -
1
; i >=
0
; i--)
103.
{
104.
final
View victim = activeViews[i];
105.
if
(victim !=
null
)
106.
{
107.
activeViews[i] =
null
;
108.
recycleViews.put(i, victim);
109.
}
110.
}
111.
112.
pruneRecycleViews();
113.
}
114.
115.
/** 确保废弃的view不多于活动的view容器的总数量 */
116.
private
void
pruneRecycleViews()
117.
{
118.
final
int
maxViews = activeViews.length;
119.
int
size = recycleViews.size();
120.
final
int
extras = size - maxViews;
121.
size--;
122.
for
(
int
j =
0
; j < extras; j++)
123.
{
124.
recycleViews.remove(recycleViews.keyAt(size--));
125.
}
126.
}
127.
128.
}