我们没有在使用TypedArray后调用recycle,编译器会提示“This TypedArray should be recycled after use with #recycle()”。
官方的解释是:回收TypedArray,以便后面重用。在调用这个函数后,你就不能再使用这个TypedArray。
在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。TypedArray
内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。你可以看看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 }
参考链接
http://stackoverflow.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 方法,得到下面一个简短的回答:
简单翻译下来,就是说:回收 TypedArray,用于后续调用时可复用之。当调用该方法后,不能再操作该变量。
同样是一个简洁的答复,但没有解开我们心中的疑惑,这个TypedArray背后,到底隐藏着怎样的秘密……
求之不得,辗转反侧,于是我们决定深入源码,一探其究竟……
首先,是 TypedArray 的常规使用方法:
- TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
- R.styleable.PieChart,0,0);
- try {
- mShowText = array.getBoolean(R.styleable.PieChart_showText,false);
- mTextPos = array.getInteger(R.styleable.PieChart_labelPosition,0);
- }finally {
- array.recycle();
- }
TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PieChart,0,0);
try {
mShowText = array.getBoolean(R.styleable.PieChart_showText,false);
mTextPos = array.getInteger(R.styleable.PieChart_labelPosition,0);
}finally {
array.recycle();
}
可见,TypedArray不是我们new出来的,而是调用了 obtainStyledAttributes 方法得到的对象,该方法实现如下:
- public TypedArray obtainStyledAttributes(AttributeSet set,
- int[] attrs, int defStyleAttr, int defStyleRes) {
- final int len = attrs.length;
- final TypedArray array = TypedArray.obtain(Resources.this, len);
- // other code .....
- return array;
- }
public TypedArray obtainStyledAttributes(AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
// other code .....
return array;
}
我们只关注当前待解决的问题,其他的代码忽略不看。从上面的代码片段得知,TypedArray也不是它实例化的,而是调用了TypedArray的一个静态方法,得到一个实例,再做一些处理,最后返回这个实例。看到这里,我们似乎知道了什么,,,带着猜测,我们进一步查看该静态方法的内部实现:
- /**
- * Container for an array of values that were retrieved with
- * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
- * or {@link Resources#obtainAttributes}. Be
- * sure to call {@link #recycle} when done with them.
- *
- * The indices used to retrieve values from this structure correspond to
- * the positions of the attributes given to obtainStyledAttributes.
- */
- public class TypedArray {
-
- static TypedArray obtain(Resources res, int len) {
- final TypedArray attrs = res.mTypedArrayPool.acquire();
- if (attrs != null) {
- attrs.mLength = len;
- attrs.mRecycled = false;
-
- final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
- if (attrs.mData.length >= fullLen) {
- return attrs;
- }
-
- attrs.mData = new int[fullLen];
- attrs.mIndices = new int[1 + len];
- return attrs;
- }
-
- return new TypedArray(res,
- new int[len*AssetManager.STYLE_NUM_ENTRIES],
- new int[1+len], len);
- }
- // Other members ......
- }
/**
* Container for an array of values that were retrieved with
* {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* or {@link Resources#obtainAttributes}. Be
* sure to call {@link #recycle} when done with them.
*
* The indices used to retrieve values from this structure correspond to
* the positions of the attributes given to obtainStyledAttributes.
*/
public class TypedArray {
static TypedArray obtain(Resources res, int len) {
final TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs != null) {
attrs.mLength = len;
attrs.mRecycled = false;
final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
if (attrs.mData.length >= fullLen) {
return attrs;
}
attrs.mData = new int[fullLen];
attrs.mIndices = new int[1 + len];
return attrs;
}
return new TypedArray(res,
new int[len*AssetManager.STYLE_NUM_ENTRIES],
new int[1+len], len);
}
// Other members ......
}
仔细看一下这个方法的实现,我想大部分人都明了了,该类没有公共的构造函数,只提供静态方法获取实例,显然是一个典型的单例模式。在代码片段的第 13 行,很清晰的表达了这个 array 是从一个 array pool的池中获取的。
因此,我们得出结论:
程序在运行时维护了一个 TypedArray的池,程序调用时,会向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被其他模块复用。
那为什么要使用这种模式呢?答案也很简单,TypedArray的使用场景之一,就是上述的自定义View,会随着 Activity的每一次Create而Create,因此,需要系统频繁的创建array,对内存和性能是一个不小的开销,如果不使用池模式,每次都让GC来回收,很可能就会造成OutOfMemory。
这就是使用池+单例模式的原因,这也就是为什么官方文档一再的强调:使用完之后一定 recycle,recycle,recycle。
最近封装一些功能性的jar包,因为需要产生一些动作,然后给调用者一些回调,所以用到了事件和监听器。
举个例子,比如DragListener和DragEvent,最开始写的时候,每次Drag动作都触发一个DragEvent事件,然后就得new一个DragEvent对象。后来感觉这样太浪费内存了,然后就研究了一下系统的MotionEvent这个类,找到了好的 解决方案。
MotionEvent的构造方法是匿名的,不能直接创建,对外提供的获取对象的接口是静态的obtain方法,可以从一个MotionEvent对象获取,也可以从一些变量获取。为什么说它是个好的解决方案呢,因为它提供了一个recycle方法 ,可以将当前的对象回收,下次要用的时候就不用重新再new一个新的对象了,直接从它的回收池里面拿就行。
下面讲解一下,MotionEvent里面有几个比较重要的变量,如下 :
代码
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方法如下:
代码
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
}
其它几个obtain方法都首先调用obtain()方法从回收栈中获取对象,然后赋值。
它的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
根据这个思路,我也做了一个Event,同样的回收原理,使得事件触发频繁的时候,大大的节约了内存的使用