零、應該掌握的
- TypedArray 的 基本用法;
- TypedArray 對象是如何生成的?
- TypedArray 與單例模式
- SynchronizedPool 同步對象池、SimplePool 簡單對象池、Pool 接口
- equals 與 == 的區別
一、思考Why:
在 Android 自定義 View 的時候,需要使用 TypedArray 來獲取 XML layout 中的屬性值,使用完之後,需要調用 recyle() 方法將 TypedArray 回收。
那麼問題來了,這個TypedArray是個什麼東西?爲什麼需要回收呢?TypedArray並沒有佔用IO,線程,它僅僅是一個變量而已,爲什麼需要 recycle?
爲了解開這個謎,首先去找官網的 Documentation,到找 TypedArray 方法,得到下面一個簡短的回答:
告訴我們在確定使用完之後調用 recycle() 方法。於是進一步查看該方法的解釋,如下:
簡單翻譯下來,就是說:回收 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不是我們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;
}
我們只關注當前待解決的問題,其他的代碼忽略不看。從上面的代碼片段得知,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 ......
}
仔細看一下這個方法的實現,我想大部分人都明瞭了,該類沒有公共的構造函數,只提供靜態方法獲取實例,顯然是一個典型的單例模式。在代碼片段的第 13 行,很清晰的表達了這個 array 是從一個 array pool的池中獲取的。
因此,我們得出結論:
程序在運行時維護了一個 TypedArray的池,程序調用時,會向該池中請求一個實例,用完之後,調用 recycle() 方法來釋放該實例,從而使其可被其他模塊複用。
那爲什麼要使用這種模式呢?答案也很簡單,TypedArray的使用場景之一,就是上述的自定義View,會隨着 Activity的每一次Create而Create,因此,需要系統頻繁的創建array,對內存和性能是一個不小的開銷,如果不使用池模式,每次都讓GC來回收,很可能就會造成OutOfMemory。
這就是使用池+單例模式的原因,這也就是爲什麼官方文檔一再的強調:使用完之後一定 recycle,recycle,recycle。
三、mTypedArrayPool 源碼簡單分析
當在自定義View 中 調用:
mTypedArray.recycle(); //回收
緊接着
public void recycle() {
if (mRecycled) {
throw new RuntimeException(toString() + " recycled twice!");
}
mRecycled = true;
// These may have been set by the client.
mXml = null;
mTheme = null;
mResources.mTypedArrayPool.release(this);
}
這個TypedArray 的公共方法有兩個細節:
(1)、主題資源置空
mXml = null;
mTheme = null;
// XmlBlock.Parser mXml;
// Resources.Theme mTheme;
(2)、調用 mResources.mTypedArrayPool.release(this) 方法
我們先看看 mTypedArrayPool 是什麼?
/**
* Synchronized) pool of objects.
* 同步對象池
* @param <T> The pooled type.
*/
public static class SynchronizedPool<T> extends SimplePool<T> {
private final Object mLock = new Object(); //鎖對象
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SynchronizedPool(int maxPoolSize) {
super(maxPoolSize);
}
@Override
public T acquire() {
synchronized (mLock) {
return super.acquire(); // 取出一個對象
}
}
@Override
public boolean release(T element) {
synchronized (mLock) {
return super.release(element); // 釋放一個對象
}
}
}
可以看到:
- SynchronizedPool 是 android.support.v4.util包下的 Pools 類的內部靜態類;
- SynchronizedPool 是 SimplePool 的子類;
- SimplePool 實現了 Pool 接口,並實現了 acquire() 方法和release(T instance) 方法;
同時可以從類 SimplePool 的構造方法中看出,mPool是一個Object 數組:
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize];
}
再回到 mTypedArrayPool 初始化的地方:
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
就知道 mTypedArrayPool 其實就是一個長度爲5 的 對象數組,當調用 TypedArray.obtain()方法的時候,就調用 final TypedArray attrs = res.mTypedArrayPool.acquire(); 從 mTypedArrayPool中取出一個 TypedArray,然後判斷取出 的attrs 是不是 null 。如果不爲null , 就返回此對象, 如果取出的是 null ,則 執行:
return new TypedArray(res,
new int[len*AssetManager.STYLE_NUM_ENTRIES],
new int[1+len], len);
可以看到,應用最開始提供含5 個TypedArray 對象的mTypedArrayPool 供用戶使用,只有當這個5 個TypedArray 對象都被佔用(mTypedArrayPool 爲空)的時候,纔會new 一個對象。
四、mTypedArrayPool 的取對象和釋放對象
取對象,調用SimplePool 的acquire( )方法
@Override
@SuppressWarnings("unchecked")
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex]; //在數組中,從後往前取
mPool[lastPooledIndex] = null; // 取完後,最後一個對象置 null
mPoolSize--; // 對象池的長度 減一
return instance;
}
return null;
}
釋放對象調用 release(T instance) 方法:
@Override
public boolean release(T instance) {
if (isInPool(instance)) { // 對於引用類型變量,先通過 == 來判斷內存地址是否相同,進而判斷該對象是否已經在對象池中,如果已經存在則拋出異常;
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance; // 將數組最後一個爲null 的對象賦值爲instance;
mPoolSize++; // 長度加一
return true;
}
return false;
}
調用 isInPool(T instance) 判斷instance 是否已經存在對象池中:
private boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) { //== 是比較對象的地址是否相等
return true;
}
}
return false;
}
參考致謝:
(1)、官方API
(2)、關於對象池的一些分析
(3)、Object Pool