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

简介:

  在项目优化过程中,通过MAT监控发现存在一处内存泄露,反复进入某个页面,内存占用越来越大。后分析找到了泄露原因,原来是在自定义列表中,将行布局的layout文件inflate成view对象的时候,每加载一次列表就要new出一组新的view对象。因为没有对这些布局一致的view进行复用,又没法及时释放,导致了列表的行布局对象越积越多,造成内存泄露。

  解决这个oom问题,首先想到了listview加载中对convertview的回收和复用的方法。于是模仿convertview的原理写了个对view对象进行回收和复用的类,此处类名为ViewRecycler,使用后有效的解决了view对象的复用,远离这个棘手的oom问题。由于项目中需要复用的view对象布局都是一样的,此方法只考虑了复用同一布局的情况。同时,项目中的列表已有获取显示行与隐藏行的相应接口,此方法仅主要从回收与复用的逻辑层面加以实现,并未涉及任何底层代码部分。


一、以下是具体的实现过程:


1.首先ViewRecycler类需要两个容器分别用来保存活动的View对象和回收可复用的View对象。一个map用来保存回收的View(无序添加),一个数组用来记录当前显示的行号以及对应的View(有序添加)。

如下:

1
2
private  SparseArray<View> recycleViews; // 废弃的view
private  View[] activeViews =  new  View[ 0 ]; //正在使用的view


注:key为int类型的HashMap用SparseArray代替,会有更好的性能.


2.每次列表刷新或变化,就更新一次activeViews的大小。即activeViews的数组长度与当前列表的总行数一致。在刷新列表或者加载更多时,调用getCount()方法更新activeViews数组长度。

如下:

1
2
3
4
5
6
// 加载完毕
private  void  loadComplete()
{
     //.....
     mViewRecycler.getCount(mDataList.size());
}


ViewRecycler类里的getCount方法:

1
2
3
4
5
6
7
8
9
10
11
12
     /** 获取活动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。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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)
         {
         }
     };
     imageLoader.loadImage(listJson.getStatusPic(), listener);
}


ViewRecycler类里的对应方法:

(1)将行号与对应的View填充到activeViews数组里保存。添加前对行号与activeViews的长度进行校正,避免越界。

1
2
3
4
5
6
7
8
9
10
11
12
     /** 添加记录当前活动的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对象。

1
2
3
4
5
6
7
8
9
10
/** 获取某个活动view */
public  View getActiveView( int  position)
{
     final  int  length =  this .activeViews.length;
     if  (position > length -  1 )
     {
         getCount(position +  1 );
     }
     return  activeViews[position];
}


(3)获取已回收的View对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/** 获取回收的view */
View getRecycleView( int  position)
{
     return  retrieveFromRecycle(recycleViews, position);
}
/** 检索回收的view */
static  View retrieveFromRecycle(SparseArray<View> 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 ;
     }
}


4.列表每隐藏一行,将消失的行布局对应的View对象回收,添加到recycleViews容器里,同时移除activeViews里的这个View。然后再进行比较并清除recycleViews容器里的回收对象,保证回收对象总数不多于活动view容器的总长度。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 隐藏
@Override
public  void  onInvalidateItem( int  id)
{
     super .onInvalidateItem(id);
     // .....
                                                                                                                                                                                          
                                                                                                                                                                                          
     // 回收view对象
     View recycleView = mViewRecycler.getActiveView(id);
     if  (recycleView !=  null )
     {
         mViewRecycler.addRecycleView(id, recycleView);
     }
                                                                                                                                                                                          
}


ViewRecycler类里的对应方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
     /** 添加废弃的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());
     }


1
2
3
4
5
6
7
8
9
10
11
12
/** 确保废弃的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。

如下:

1
2
3
4
5
6
7
@Override
public  void  onDestroy()
{
     super .onDestroy();
     //销毁所有view对象
     mViewRecycler.recycleAllActiveViews();
}


ViewRecycler类里的对应方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 将所有剩余的活动view移到废弃view里 */
void  recycleAllActiveViews()
{
     final  View[] activeViews =  this .activeViews;
     SparseArray<View> 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();
}



二、调试结果

滑动时,请求加载行数据。每次添加一个行布局对象,当达到10行后开始执行View对象回收。每次将栈底的View回收并复用到栈顶的行布局里。如下图:

wKioL1MT8iTS8UokAAbh95qOXfU070.jpg

wKiom1MT8kqy3pXUAAIx3_1C-iQ550.jpg



三、最后附上完整的ViewRecycler类

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
public  class  ViewRecycler
{
     private  SparseArray<View> recycleViews; // 废弃的view
     private  View[] activeViews =  new  View[ 0 ]; //正在使用的view
                                                 
     public  ViewRecycler()
     {
         recycleViews =  new  SparseArray<View>();
     }
                                              
     /** 获取活动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<View> 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<View> 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--));
         }
     }
                                                 
}


    方法仅供参考,具体实现过程还需根据项目实际需求进行修改或优化。欢迎各路高手不吝赐教,多多交流!





本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1366319,如需转载请自行联系原作者

目录
相关文章
|
8月前
|
数据库 Android开发
Android使用EditText+Listview实现搜索效果(使用room模糊查询)
本文介绍如何在Android中使用EditText与ListView实现搜索功能,并结合Room数据库完成模糊查询。主要内容包括:Room的模糊查询语句(使用`||`代替`+`号)、布局美化(如去除ListView分割线和EditText下划线)、EditText回车事件监听,以及查询逻辑代码示例。此外,还提供了相关扩展文章链接,帮助读者深入了解ListView优化、动态搜索及Room基础操作。
581 65
|
8月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
572 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
4月前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
153 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
8月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
161 0
|
8月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
397 65
Android自定义view之网易云推荐歌单界面
|
8月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
754 84
|
6月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
453 11
|
8月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
250 3
|
缓存 Android开发 开发者
Android性能:经典ListView适配器convertView缓存及复用机制
Android性能:经典ListView适配器convertView缓存及复用机制 Android中的ListView常用Adapter中都会涉及到convertView的使用,使用convertView主要是为了缓存试图View,用以增加ListView的item view加载效率。
1734 0
|
3月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
367 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡

热门文章

最新文章