TypedArray 爲什麼需要調用recycle()

零、應該掌握的

  1. TypedArray 的 基本用法;
  2. TypedArray 對象是如何生成的?
  3. TypedArray 與單例模式
  4. SynchronizedPool 同步對象池、SimplePool 簡單對象池、Pool 接口
  5. 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); // 釋放一個對象
            }
        }
    }

可以看到:

  1. SynchronizedPool 是 android.support.v4.util包下的 Pools 類的內部靜態類;
  2. SynchronizedPool 是 SimplePool 的子類;
  3. 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

發佈了102 篇原創文章 · 獲贊 76 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章