Android项目:模仿ConvertView原理(ListView的getView方法)对View对象进行回收和复用

本文详细介绍了如何在项目中解决内存泄露问题,特别是在自定义列表中通过复用view对象来避免内存占用逐渐增大。通过实现ViewRecycler类,有效地回收和复用了view对象,远离了内存泄漏的困扰。文章提供了具体实现过程,包括view对象的添加、获取、回收等关键步骤,并展示了调试结果和完整代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在项目优化过程中,通过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回收并复用到栈顶的行布局里。如下图:

wKioL1MT8iTS8UokAAbh95qOXfU070.jpg

wKiom1MT8kqy3pXUAAIx3_1C-iQ550.jpg

 

 

三、最后附上完整的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.}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值