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




我们没有在使用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 的常规使用方法:

  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 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 方法得到的对象,该方法实现如下:

  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. }  
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的一个静态方法,得到一个实例,再做一些处理,最后返回这个实例。看到这里,我们似乎知道了什么,,,带着猜测,我们进一步查看该静态方法的内部实现:

  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. }  
/**
 * 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。


Android系统事件的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,同样的回收原理,使得事件触发频繁的时候,大大的节约了内存的使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 中,你可以通过以下步骤在代码中动态调用自定义属性: 1. 首先,在 res/values/attrs.xml 文件中定义自定义属性。例如,我们定义一个名为 customMargin 的自定义属性: ```xml <resources> <declare-styleable name="CustomView"> <attr name="customMargin" format="dimension" /> </declare-styleable> </resources> ``` 2. 在你的自定义视图类中获取和使用自定义属性的值。例如,我们在 CustomView 类中获取 customMargin 的值并应用到视图上: ```java public class CustomView extends View { private int customMargin; public CustomView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } private void init(AttributeSet attrs) { TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CustomView); customMargin = typedArray.getDimensionPixelSize(R.styleable.CustomView_customMargin, 0); typedArray.recycle(); // 在这里可以根据 customMargin 的值做相应的操作,例如设置边距等 // ... } } ``` 在上述示例中,我们通过 getContext().obtainStyledAttributes(attrs, R.styleable.CustomView) 方法获取 TypedArray 对象,然后使用 getDimensionPixelSize 方法获取 customMargin 属性的值。 3. 在 XML 布局文件中使用自定义视图,并设置自定义属性的值。例如: ```xml <com.example.CustomView android:layout_width="match_parent" android:layout_height="wrap_content" app:customMargin="16dp" /> ``` 在上述示例中,我们使用 app:customMargin="16dp" 来设置 customMargin 的值为 16dp。 通过以上步骤,你可以在代码中动态调用自定义属性,并根据其值做相应的操作。希望对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值