在项目优化过程中,通过MAT监控发现存在一处内存泄露,反复进入某个页面,内存占用越来越大。后分析找到了泄露原因,原来是在自定义列表中,将行布局的layout文件inflate成view对象的时候,每加载一次列表就要new出一组新的view对象。因为没有对这些布局一致的view进行复用,又没法及时释放,导致了列表的行布局对象越积越多,造成内存泄露。
解决这个oom问题,首先想到了listview加载中对convertview的回收和复用的方法。于是模仿convertview的原理写了个对view对象进行回收和复用的类,此处类名为ViewRecycler,使用后有效的解决了view对象的复用,远离这个棘手的oom问题。由于项目中需要复用的view对象布局都是一样的,此方法只考虑了复用同一布局的情况。同时,项目中的列表已有获取显示行与隐藏行的相应接口,此方法仅主要从回收与复用的逻辑层面加以实现,并未涉及任何底层代码部分。
一、以下是具体的实现过程:
1.首先ViewRecycler类需要两个容器分别用来保存活动的View对象和回收可复用的View对象。一个map用来保存回收的View(无序添加),一个数组用来记录当前显示的行号以及对应的View(有序添加)。
如下:
private SparseArrayrecycleViews;// 废弃的view private View[] activeViews = new View[0];//正在使用的view
注:key为int类型的HashMap用SparseArray代替,会有更好的性能.
2.每次列表刷新或变化,就更新一次activeViews的大小。即activeViews的数组长度与当前列表的总行数一致。在刷新列表或者加载更多时,调用getCount()方法更新activeViews数组长度。
如下:
// 加载完毕 private void loadComplete() { //..... mViewRecycler.getCount(mDataList.size()); }
ViewRecycler类里的getCount方法:
/** 获取活动view的总数 */ public void getCount(int count) { final int length = this.activeViews.length; if (count > length) { final View[] activeViews = this.activeViews; this.activeViews = Arrays.copyOf(activeViews, count); // Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]); } }
3.列表每新增显示一行,就先获取是否有可复用的View对象。先判断recycleViews是否已存有该行号对应的View,没有则获取最新回收的View。再结合setTag与getTag便可实现对回收View对象的复用了。如果recycleViews没有可复用的View,则inflate生成新的View。
如下:
public void convertFromView(final ListJson listJson, final int n) { ViewHolder holder = null; // 判断是否有可重复利用的view View resycleView = mViewRecycler.getRecycleView(n); if (resycleView == null) { resycleView = LayoutInflater.from(getActivity()).inflate(R.layout.list_item, null); holder = new ViewHolder(); //..... holder.iv_main = (ImageView) resycleView.findViewById(R.id.item_iv_main); resycleView.setTag(holder); } else { holder = (ViewHolder) resycleView.getTag(); } //保存新增活动的View对象 mViewRecycler.addActiveView(n, resycleView); // 加载行布局数据 holder.tv_title.setText(lison.getName()); holder.tv_price.setText("待定"); //..... // 下载图片 ImageLoadingListener listener = new ImageLoadingListener() { @Override public void onLoadingStarted(String arg0, View arg1) { } @Override public void onLoadingComplete(String arg0, View arg1, Bitmap bitmap) { //获取view对象 View bitmapView = mViewRecycler.getActiveView(n); if (bitmapView != null) { ViewHolder holder = (ViewHolder) bitmapView.getTag(); if (holder != null) { // 加载图片 holder.iv_main.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.bg_item)); } } } @Override public void onLoadingFailed(String arg0, View arg1, FailReason arg2) { } @Override public void onLoadingCancelled(String arg0, View arg1) { } }; p_w_picpathLoader.loadImage(listJson.getStatusPic(), listener); }
ViewRecycler类里的对应方法:
(1)将行号与对应的View填充到activeViews数组里保存。添加前对行号与activeViews的长度进行校正,避免越界。
/** 添加记录当前活动的view */ public void addActiveView(int position, View view) { final int length = this.activeViews.length; if (position > length - 1) { getCount(position + 1); } this.activeViews[position] = view; // Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews)); }
(2)根据行号获取对应的View对象。
/** 获取某个活动view */ public View getActiveView(int position) { final int length = this.activeViews.length; if (position > length - 1) { getCount(position + 1); } return activeViews[position]; }
(3)获取已回收的View对象。
/** 获取回收的view */ View getRecycleView(int position) { return retrieveFromRecycle(recycleViews, position); } /** 检索回收的view */ static View retrieveFromRecycle(SparseArrayrecycleViews, int position) { int size = recycleViews.size(); if (size > 0) { // See if we still have a view for this position. for (int i = 0; i < size; i++) { int fromPosition = recycleViews.keyAt(i); View view = recycleViews.get(fromPosition); if (fromPosition == position) { recycleViews.remove(fromPosition); return view; } } int index = size - 1; View r = recycleViews.valueAt(index); recycleViews.remove(recycleViews.keyAt(index)); return r; } else { return null; } }
4.列表每隐藏一行,将消失的行布局对应的View对象回收,添加到recycleViews容器里,同时移除activeViews里的这个View。然后再进行比较并清除recycleViews容器里的回收对象,保证回收对象总数不多于活动view容器的总长度。
如下:
// 隐藏 @Override public void onInvalidateItem(int id) { super.onInvalidateItem(id); // ..... // 回收view对象 View recycleView = mViewRecycler.getActiveView(id); if (recycleView != null) { mViewRecycler.addRecycleView(id, recycleView); } }
ViewRecycler类里的对应方法:
/** 添加废弃的view,无序添加 */ void addRecycleView(int position, View scrap) { recycleViews.put(position, scrap); final int length = this.activeViews.length; if (position < length) { this.activeViews[position] = null; } pruneRecycleViews(); // Log.e("Recycle", "Recycle.size() = " +recycleViews.size()); }
/** 确保废弃的view总数不多于活动的view容器的长度(此方法可再改进为不多于当前活动的View对象数量) */ private void pruneRecycleViews() { final int maxViews = activeViews.length; int size = recycleViews.size(); final int extras = size - maxViews; size--; for (int j = 0; j < extras; j++) { recycleViews.remove(recycleViews.keyAt(size--)); } }
5.退出时,清除所有View对象及其引用。
以下方法则是根据项目需求,将所有的活动view移到recycleViews里,再整理recycleViews。
如下:
@Override public void onDestroy() { super.onDestroy(); //销毁所有view对象 mViewRecycler.recycleAllActiveViews(); }
ViewRecycler类里的对应方法:
/** 将所有剩余的活动view移到废弃view里 */ void recycleAllActiveViews() { final View[] activeViews = this.activeViews; SparseArrayrecycleViews = this.recycleViews; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { activeViews[i] = null; recycleViews.put(i, victim); } } pruneRecycleViews(); }
二、调试结果
滑动时,请求加载行数据。每次添加一个行布局对象,当达到10行后开始执行View对象回收。每次将栈底的View回收并复用到栈顶的行布局里。如下图:
三、最后附上完整的ViewRecycler类
如下:
public class ViewRecycler { private SparseArrayrecycleViews;// 废弃的view private View[] activeViews = new View[0];//正在使用的view public ViewRecycler() { recycleViews = new SparseArray (); } /** 获取活动view的总数 */ public void getCount(int count) { final int length = this.activeViews.length; if (count > length) { final View[] activeViews = this.activeViews; this.activeViews = Arrays.copyOf(activeViews, count); // Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]); } } /** 添加记录当前活动的view */ public void addActiveView(int position, View view) { final int length = this.activeViews.length; if (position > length - 1) { getCount(position + 1); } this.activeViews[position] = view; // Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews)); } /** 获取某个活动view */ public View getActiveView(int position) { final int length = this.activeViews.length; if (position > length - 1) { getCount(position + 1); } return activeViews[position]; } /** 获取废弃的view */ View getRecycleView(int position) { return retrieveFromRecycle(recycleViews, position); } /** 检索废弃的view */ static View retrieveFromRecycle(SparseArray recycleViews, int position) { int size = recycleViews.size(); if (size > 0) { // See if we still have a view for this position. for (int i = 0; i < size; i++) { int fromPosition = recycleViews.keyAt(i); View view = recycleViews.get(fromPosition); if (fromPosition == position) { recycleViews.remove(fromPosition); return view; } } int index = size - 1; View r = recycleViews.valueAt(index); recycleViews.remove(recycleViews.keyAt(index)); return r; } else { return null; } } /** 添加废弃的view,无序添加 */ void addRecycleView(int position, View scrap) { recycleViews.put(position, scrap); final int length = this.activeViews.length; if (position < length) { this.activeViews[position] = null; } pruneRecycleViews(); // Log.e("Recycle", "Recycle.size() = " +recycleViews.size()); } /** 将所有剩余的活动view移到废弃view里 */ void recycleAllActiveViews() { final View[] activeViews = this.activeViews; SparseArray recycleViews = this.recycleViews; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { activeViews[i] = null; recycleViews.put(i, victim); } } pruneRecycleViews(); } /** 确保废弃的view不多于活动的view容器的总数量 */ private void pruneRecycleViews() { final int maxViews = activeViews.length; int size = recycleViews.size(); final int extras = size - maxViews; size--; for (int j = 0; j < extras; j++) { recycleViews.remove(recycleViews.keyAt(size--)); } } }
方法仅供参考,具体实现过程还需根据项目实际需求进行修改或优化。欢迎各路高手不吝赐教,多多交流!
参考资料:
转载请注明出处:http://glblong.blog.51cto.com/3058613/1366319