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,如需转载请自行联系原作者

目录
相关文章
|
1月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
85 0
|
25天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
48 15
Android 系统缓存扫描与清理方法分析
|
18天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
24天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
53 8
|
26天前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
26天前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
30 2
|
27天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
30天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
24 2
|
缓存 安全 开发工具
Android 解决bug:Android studio 运行、编译项目时导致电脑死机
Android 解决bug:Android studio 运行、编译项目时导致电脑死机
900 0
|
Android开发
Android Studio在android Emulator中运行的项目黑屏
Android Studio在android Emulator中运行的项目黑屏
752 0
Android Studio在android Emulator中运行的项目黑屏
下一篇
无影云桌面