Android之TypedArray 为什么需要调用recycle()

时间:2022-09-29 10:46:49



我们没有在使用TypedArray后调用recycle,编译器会提示“This TypedArray should be recycled after use with #recycle()”。

官方的解释是:回收TypedArray,以便后面重用。在调用这个函数后,你就不能再使用这个TypedArray。

在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。你可以看看TypedArray.recycle()中的代码:

Android之TypedArray 为什么需要调用recycle()
 1 /**
2 * Give back a previously retrieved StyledAttributes, for later re-use.
3 */
4 public void recycle() {
5 synchronized (mResources.mTmpValue) {
6 TypedArray cached = mResources.mCachedStyledAttributes;
7 if (cached == null || cached.mData.length < mData.length) {
8 mXml = null;
9 mResources.mCachedStyledAttributes = this;
10 }
11 }
12 }
Android之TypedArray 为什么需要调用recycle()

 

 

参考链接

http://*.com/questions/13805502/why-do-you-have-to-recycle-a-typedarray

http://developer.android.com/reference/android/content/res/TypedArray.html#recycle%28%29


转自:http://blog.csdn.net/Monicabg/article/details/45014327

在 Android 自定义 View 的时候,需要使用 TypedArray 来获取 XML layout 中的属性值,使用完之后,需要调用 recyle() 方法将 TypedArray 回收。

那么问题来了,这个TypedArray是个什么东西?为什么需要回收呢?TypedArray并没有占用IO,线程,它仅仅是一个变量而已,为什么需要 recycle? 
为了解开这个谜,首先去找官网的 Documentation,到找 TypedArray 方法,得到下面一个简短的回答:

Android之TypedArray 为什么需要调用recycle()

简单翻译下来,就是说:回收 TypedArray,用于后续调用时可复用之。当调用该方法后,不能再操作该变量。

同样是一个简洁的答复,但没有解开我们心中的疑惑,这个TypedArray背后,到底隐藏着怎样的秘密……

求之不得,辗转反侧,于是我们决定深入源码,一探其究竟……

首先,是 TypedArray 的常规使用方法:

  1. TypedArray array = context.getTheme().obtainStyledAttributes(attrs,  
  2.                 R.styleable.PieChart,0,0);  
  3. try {  
  4.     mShowText = array.getBoolean(R.styleable.PieChart_showText,false);  
  5.     mTextPos = array.getInteger(R.styleable.PieChart_labelPosition,0);  
  6. }finally {  
  7.     array.recycle();  
  8. }  
可见,TypedArray不是我们new出来的,而是调用了 obtainStyledAttributes 方法得到的对象,该方法实现如下:

  1. public TypedArray obtainStyledAttributes(AttributeSet set,  
  2.                 int[] attrs, int defStyleAttr, int defStyleRes) {  
  3.     final int len = attrs.length;  
  4.     final TypedArray array = TypedArray.obtain(Resources.this, len);  
  5.     // other code .....  
  6.     return array;  
  7. }  
我们只关注当前待解决的问题,其他的代码忽略不看。从上面的代码片段得知,TypedArray也不是它实例化的,而是调用了TypedArray的一个静态方法,得到一个实例,再做一些处理,最后返回这个实例。看到这里,我们似乎知道了什么,,,带着猜测,我们进一步查看该静态方法的内部实现:

  1. /**  
  2.  * Container for an array of values that were retrieved with  
  3.  * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}  
  4.  * or {@link Resources#obtainAttributes}.  Be  
  5.  * sure to call {@link #recycle} when done with them.  
  6.  *  
  7.  * The indices used to retrieve values from this structure correspond to  
  8.  * the positions of the attributes given to obtainStyledAttributes.  
  9.  */  
  10. public class TypedArray {  
  11.   
  12.     static TypedArray obtain(Resources res, int len) {  
  13.         final TypedArray attrs = res.mTypedArrayPool.acquire();  
  14.         if (attrs != null) {  
  15.             attrs.mLength = len;  
  16.             attrs.mRecycled = false;  
  17.   
  18.             final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;  
  19.             if (attrs.mData.length >= fullLen) {  
  20.                 return attrs;  
  21.             }  
  22.   
  23.             attrs.mData = new int[fullLen];  
  24.             attrs.mIndices = new int[1 + len];  
  25.             return attrs;  
  26.         }  
  27.   
  28.         return new TypedArray(res,  
  29.                 new int[len*AssetManager.STYLE_NUM_ENTRIES],  
  30.                 new int[1+len], len);  
  31.     }  
  32.     // Other members ......  
  33. }  

仔细看一下这个方法的实现,我想大部分人都明了了,该类没有公共的构造函数,只提供静态方法获取实例,显然是一个典型的单例模式。在代码片段的第 13 行,很清晰的表达了这个 array 是从一个 array pool的池中获取的。

因此,我们得出结论:

程序在运行时维护了一个 TypedArray的池,程序调用时,会向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被其他模块复用。

那为什么要使用这种模式呢?答案也很简单,TypedArray的使用场景之一,就是上述的自定义View,会随着 Activity的每一次Create而Create,因此,需要系统频繁的创建array,对内存和性能是一个不小的开销,如果不使用池模式,每次都让GC来回收,很可能就会造成OutOfMemory。

这就是使用池+单例模式的原因,这也就是为什么官方文档一再的强调:使用完之后一定 recycle,recycle,recycle。


Android系统事件的recycle原理

最近封装一些功能性的jar包,因为需要产生一些动作,然后给调用者一些回调,所以用到了事件和监听器。 
举个例子,比如DragListener和DragEvent,最开始写的时候,每次Drag动作都触发一个DragEvent事件,然后就得new一个DragEvent对象。后来感觉这样太浪费内存了,然后就研究了一下系统的MotionEvent这个类,找到了好的解决方案。

MotionEvent的构造方法是匿名的,不能直接创建,对外提供的获取对象的接口是静态的obtain方法,可以从一个MotionEvent对象获取,也可以从一些变量获取。为什么说它是个好的解决方案呢,因为它提供了一个recycle方法,可以将当前的对象回收,下次要用的时候就不用重新再new一个新的对象了,直接从它的回收池里面拿就行。

下面讲解一下,MotionEvent里面有几个比较重要的变量,如下

 

Android之TypedArray 为什么需要调用recycle()Android之TypedArray 为什么需要调用recycle()代码
 
    
1 // 变量 2   private MotionEvent mNext; // 指向回收栈的下一个对象 3   private boolean mRecycled; // 标志是否是被回收掉的对象 4   // 静态变量 5 static private final int MAX_RECYCLED = 10 ; // 最大可回收的数目 6 static private Object gRecyclerLock = new Object(); // 锁定整个类用的 7 static private int gRecyclerUsed = 0 ; // 回收栈中回收的对象数目 8 static private MotionEvent gRecyclerTop = null ; // 回收栈的栈顶对象

 

    然后有一个静态的obtain方法:

  其它几个obtain方法都首先调用obtain()方法从回收栈中获取对象,然后赋值。

      它的recycle方法如下:

Android之TypedArray 为什么需要调用recycle()Android之TypedArray 为什么需要调用recycle()代码
Android之TypedArray 为什么需要调用recycle()
 
    
1 static private MotionEvent obtain() { 2 synchronized (gRecyclerLock) { // 锁住整个类 3 if (gRecyclerTop == null ) { // 栈顶不存在,就new一个新的 4 return new MotionEvent(); 5 } 6 MotionEvent ev = gRecyclerTop; // 栈顶存在,就用一个引用ev指向它 7 gRecyclerTop = ev.mNext; // 然后把栈顶的下一个对象提到栈顶 8 gRecyclerUsed -- ; // 回收栈中的对象数目减少一个 9 ev.mRecycledLocation = null ; // 是一个异常,作用未知 10 ev.mRecycled = false ; // 当前对象标志为未回收状态 11 return ev; 12 } 13 }
Android之TypedArray 为什么需要调用recycle()

  其它几个obtain方法都首先调用obtain()方法从回收栈中获取对象,然后赋值。

      它的recycle方法如下:

Android之TypedArray 为什么需要调用recycle()Android之TypedArray 为什么需要调用recycle()代码
Android之TypedArray 为什么需要调用recycle()
 
    
1 public void recycle() { 2 // 确保recycle方法只调用一次 3 if (TRACK_RECYCLED_LOCATION) { 4 if (mRecycledLocation != null ) { 5 throw new RuntimeException(toString() + " recycled twice! " , mRecycledLocation); 6 } 7 mRecycledLocation = new RuntimeException( " Last recycled here " ); 8 } else if (mRecycled) { 9 throw new RuntimeException(toString() + " recycled twice! " ); 10 } 11 12 synchronized (gRecyclerLock) { // 锁住类 13 if (gRecyclerUsed < MAX_RECYCLED) { // 如果回收栈中的对象还没达到最大值 14 gRecyclerUsed ++ ; // 回收栈中元素数目增加1 15 mNumHistory = 0 ; 16 // 这两句是把当前对象的next指向以前的栈顶,然后把当前对象放到栈顶 17 mNext = gRecyclerTop; 18 gRecyclerTop = this ; 19 } 20 } 21 } 22
Android之TypedArray 为什么需要调用recycle()

根据这个思路,我也做了一个Event,同样的回收原理,使得事件触发频繁的时候,大大的节约了内存的使用