Android簡(jiǎn)單內(nèi)存緩存

一. 問(wèn)題

前段時(shí)間在公司項(xiàng)目中遇到一個(gè)問(wèn)題疫鹊,在集成百度地圖時(shí)想要在地圖上動(dòng)態(tài)添加 ICON动壤,這些 ICON 都是隨時(shí)可變的逃呼,起初設(shè)計(jì)時(shí)做裙,每次添加的 ICON 都是動(dòng)態(tài) new 出一個(gè)新的Bitmap,這樣可以滿足 ICON 隨時(shí)可變的需求嗅蔬,但是需求完成之后剑按,通過(guò) AS 觀察到 APP 內(nèi)存飆升,APP 內(nèi)存吃緊帶來(lái)的壞處我就不再贅述了澜术,為了解決這個(gè)問(wèn)題艺蝴,我就設(shè)計(jì)了一個(gè)簡(jiǎn)單的內(nèi)存緩存框架來(lái)解決這個(gè)問(wèn)題,有效的減少了 APP 的內(nèi)存消耗鸟废。

二. 解決

解決問(wèn)題時(shí)的問(wèn)題思路我就不再說(shuō)了猜敢,直接看下源碼~

/**
 * Created by daiyiming on 2016/12/10.
 * 本地內(nèi)存緩存
 */
public final class MemoryCache<KO, VO> {

    public static final int CAPACITY_DEFAULT = 10; // 默認(rèn)緩存
    public static final int CAPACITY_INFINITY = -1; // 無(wú)限緩存
    public static final int MAX_CAPACITY_DEFAULT = 20; // 默認(rèn)最大緩存
    public static final int MAX_CAPACITY_INFINITY = -1; // 無(wú)限最大緩存

    private final LinkedList<ValueHolder<KO, VO>> mCacheList; // 緩存
    private final HashSet<KO> mKeySet; // 鍵緩存
    private volatile int mCapacity; // 容量
    private volatile int mMaxCapacity; // 最大容量
    private volatile boolean mAllowUpdate; // 鍵值沖突時(shí)是否準(zhǔn)許更新

    /**
     * 鍵值對(duì)組合類
     *
     * @param <KI> 鍵類型
     * @param <VI> 值類型
     */
    private static final class ValueHolder<KI, VI> {
        private final KI mKey; // 鍵不準(zhǔn)許修改,若修改則用添加新的對(duì)象
        private VI mValue; // 值可以修改,用于更新

        private ValueHolder(KI key, VI value) {
            mKey = key;
            mValue = value;
        }

        /**
         * 更新值對(duì)象
         * @param value 新的值對(duì)象
         */
        private void update(VI value) {
            recycleValue();
            mValue = value;
        }

        /**
         * 回收Holder
         */
        private void recycle() {
            recycleKey();
            recycleValue();
        }

        private void recycleKey() {
            if (mKey instanceof IRecycleInterface) {
                ((IRecycleInterface) mKey).recycle();
            }
        }

        private void recycleValue() {
            if (mValue instanceof IRecycleInterface) {
                ((IRecycleInterface) mValue).recycle();
            }
        }

    }

    /**
     * 值接口缩擂,實(shí)現(xiàn)幫助對(duì)象鍵值內(nèi)存回收
     */
    public interface IRecycleInterface {
        void recycle();
    }

    public MemoryCache() {
        this(CAPACITY_DEFAULT, MAX_CAPACITY_DEFAULT);
    }

    public MemoryCache(int capacity, int maxCapacity) {
        if (capacity < CAPACITY_INFINITY
                || maxCapacity < MAX_CAPACITY_INFINITY) {
            throw new IllegalArgumentException("MemoryCache:構(gòu)造函數(shù)參數(shù)錯(cuò)誤");
        }
        mCacheList = new LinkedList<>();
        mKeySet = new HashSet<>();
        mCapacity = capacity;
        mMaxCapacity = maxCapacity;
        mAllowUpdate = false;
    }

    public synchronized void put(KO key, VO value) {
        if (key == null || value == null) {
            return;
        }
        if (mKeySet.contains(key)) { // 如果已經(jīng)存在則復(fù)用對(duì)象
            ListIterator<ValueHolder<KO, VO>> iterator = mCacheList.listIterator();
            while (iterator.hasNext()) {
                ValueHolder<KO, VO> holder = iterator.next();
                if (holder.mKey.equals(key)) {
                    holder.update(value);
                    // 如果不準(zhǔn)許更新則刪除重新添加
                    if (!mAllowUpdate && iterator.previousIndex() != 0) {
                        iterator.remove();
                        mCacheList.addFirst(holder);
                    }
                    break;
                }
            }
        } else { // 不存在則添加
            mKeySet.add(key);
            mCacheList.addFirst(new ValueHolder<>(key, value));
        }
        // 如果大于最大容量且不是無(wú)限容量則清除末尾一個(gè)
        if (mMaxCapacity != MAX_CAPACITY_INFINITY
                && mCacheList.size() > mMaxCapacity) {
            remove(mCacheList.size() - 1);
        }
    }

    public synchronized VO get(KO key) {
        if (mKeySet.contains(key)) {
            ListIterator<ValueHolder<KO, VO>> iterator = mCacheList.listIterator();
            while (iterator.hasNext()) {
                ValueHolder<KO, VO> holder = iterator.next();
                if (holder.mKey.equals(key)) { // 找到
                    if (iterator.previousIndex() != 0) { // 如果不是在頭部就移動(dòng)到頭部
                        iterator.remove();
                        mCacheList.addFirst(holder);
                    }
                    // 刪除一個(gè)超出容量的數(shù)據(jù)
                    if (mCapacity != CAPACITY_INFINITY
                            && mCacheList.size() > mCapacity) {
                        remove(mCacheList.size() - 1);
                    }
                    return holder.mValue;
                }
            }
        }
        return null;
    }

    public synchronized boolean contains(KO k) {
        return mKeySet.contains(k);
    }

    public synchronized void clear() {
        mKeySet.clear();
        for (ValueHolder<KO, VO> holder : mCacheList) {
            holder.recycle();
        }
        mCacheList.clear();
    }

    public synchronized int size() {
        return mCacheList.size();
    }

    public synchronized void remove(KO key) {
        if (mKeySet.contains(key)) {
            ListIterator<ValueHolder<KO, VO>> iterator = mCacheList.listIterator();
            while (iterator.hasNext()) {
                ValueHolder<KO, VO> holder = iterator.next();
                if (holder.mKey.equals(key)) {
                    iterator.remove();
                    mKeySet.remove(holder.mKey);
                    holder.recycle();
                    break;
                }
            }
        }
    }

    public synchronized void remove(int position) {
        if (position >= 0 && position < size()) {
            ValueHolder<KO, VO> removedHolder = mCacheList.remove(position);
            mKeySet.remove(removedHolder.mKey);
            removedHolder.recycle();
        }
    }

    public void setCapacity(int capacity) {
        mCapacity = capacity;
    }

    public void setMaxCapacity(int maxCapacity) {
        mMaxCapacity = maxCapacity;
    }

    public int getCapacity() {
        return mCapacity;
    }

    public int getMaxCapacity() {
        return mMaxCapacity;
    }

    /**
     * 準(zhǔn)許更新
     * 決定添加過(guò)程中鍵值重復(fù)時(shí)直接原位置更新還是刪除重新添加
     *
     * @param allowUpdate 是否準(zhǔn)許更新
     */
    public void allowUpdate(boolean allowUpdate) {
        mAllowUpdate = allowUpdate;
    }

}

通過(guò)一個(gè)鏈表保存需要緩存的元素鼠冕,每次添加的新元素放到鏈表的首部,添加時(shí)檢測(cè)如果超出最大容量胯盯,則從鏈表的尾部刪除一個(gè)元素懈费,為什么這么做呢?主要是為了防止瘋狂 put 元素導(dǎo)致 OOM博脑。在每次get元素的時(shí)候憎乙,通過(guò)傳入的 key 值遍歷鏈表,當(dāng)命中想要獲取的元素的時(shí)候叉趣,將這個(gè)元素移到鏈表的首部泞边,這樣可以保證頻繁獲取的元素在鏈表靠前的位置,減少獲取時(shí)間疗杉。在 get 成功的時(shí)候阵谚,如果鏈表長(zhǎng)度大于容量(注意與最大容量的區(qū)別),我們就從列表末尾刪除一個(gè)元素烟具,因?yàn)槟┪驳脑乇砻鬟@個(gè)元素很大的可能是長(zhǎng)時(shí)間沒(méi)人使用(get操作)梢什,即可視為過(guò)期元素。

為什么要設(shè)置一個(gè)容量和最大容量的區(qū)別呢净赴?因?yàn)榭紤]到可能存在瘋狂 put 的操作绳矩,即用戶一直在 put罩润,如果最大容量大于容量玖翅,就給了最先 put 的元素有被重新拿到鏈表首部的可能性,即一定程度的加強(qiáng)了這個(gè)元素被再次利用的可能性割以,在之后的 get 的操作中金度,鏈表尾部元素逐漸被清除,鏈表長(zhǎng)度逐漸回歸正常严沥。又因?yàn)樵O(shè)置了最大容量猜极,給 put 操作設(shè)置了上限,所以基本不會(huì)有 OOM消玄。

當(dāng)然防止有些時(shí)候需要緩存的時(shí)候 IO 連接跟伏,SOCKET 連接,Bitmap 等需要手動(dòng)銷毀的東西翩瓜,我還實(shí)現(xiàn)了一個(gè) Recycle 接口受扳,如果緩存的鍵或者值需要用戶自定義銷毀操作,則實(shí)現(xiàn)這個(gè)接口即可兔跌。

具體見(jiàn)下圖

put.png
get.png

三. 使用

/**
 * Created by daiyiming on 2016/12/11.
 * 緩存執(zhí)行類
 */
public final class MarkerMemoryCache {

    private static MemoryCache<Key, Value> sLocalCache = new MemoryCache<>();

    private static void confirmEnable() {
        if (sLocalCache == null) {
            sLocalCache = new MemoryCache<>();
        }
    }

    public static void put(Key key, Value value) {
        confirmEnable();
        sLocalCache.put(key, value);
    }

    public static BitmapDescriptor get(Key key) {
        confirmEnable();
        Value value = sLocalCache.get(key);
        if (value != null) {
            return value.mBitmapDescriptor;
        }
        return null;
    }

    public static Key generateKey(int kind, String styleDetail, String styleId) {
        return new Key(kind, styleDetail == null ? "" : styleDetail, styleId == null ? "" : styleId);
    }

    public static Value generateValue(BitmapDescriptor bitmapDescriptor) {
        return new Value(bitmapDescriptor);
    }

    public static void clear() {
        sLocalCache.clear();
    }

    public static final class Key {

        private final int mKind;
        private final String mStyleDetail;
        private final String mStyleId;
        private final int mHashCode;

        private Key(int kind, String styleDetail, String styleId) {
            mKind = kind;
            mStyleDetail = styleDetail;
            mStyleId = styleId;
            mHashCode = generateHashCode();
        }

        private int generateHashCode() {
            int result = 17;
            result = 31 * result + mKind;
            result = 31 * result + mStyleDetail.hashCode();
            result = 31 * result + mStyleId.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof Key) {
                if (object == this) {
                    return true;
                }
                Key key = (Key) object;
                return mKind == key.mKind
                        && mStyleDetail.equals(key.mStyleDetail)
                        && mStyleId.equals(key.mStyleId);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }

    }

    public static final class Value implements MemoryCache.IRecycleInterface {

        private BitmapDescriptor mBitmapDescriptor = null;

        private Value(BitmapDescriptor bitmapDescriptor) {
            mBitmapDescriptor = bitmapDescriptor;
        }

        @Override
        public void recycle() {
            if (mBitmapDescriptor != null) {
                Bitmap bitmap = mBitmapDescriptor.getBitmap();
                if (bitmap != null && !bitmap.isRecycled()) {
                    bitmap.recycle();
                }
            }
        }
    }

}

這個(gè)實(shí)現(xiàn)是復(fù)雜實(shí)現(xiàn)勘高,??基本實(shí)現(xiàn)了所有功能,當(dāng)然?也可以簡(jiǎn)單使用~
第一次在簡(jiǎn)書寫記錄東西哈哈以后繼續(xù)~?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市华望,隨后出現(xiàn)的幾起案子蕊蝗,更是在濱河造成了極大的恐慌,老刑警劉巖赖舟,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓬戚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宾抓,警方通過(guò)查閱死者的電腦和手機(jī)碌更,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洞慎,“玉大人痛单,你說(shuō)我怎么就攤上這事【⑼龋” “怎么了旭绒?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)焦人。 經(jīng)常有香客問(wèn)我挥吵,道長(zhǎng),這世上最難降的妖魔是什么花椭? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任忽匈,我火速辦了婚禮,結(jié)果婚禮上矿辽,老公的妹妹穿的比我還像新娘丹允。我一直安慰自己,他們只是感情好袋倔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布雕蔽。 她就那樣靜靜地躺著,像睡著了一般宾娜。 火紅的嫁衣襯著肌膚如雪批狐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天前塔,我揣著相機(jī)與錄音嚣艇,去河邊找鬼。 笑死华弓,一個(gè)胖子當(dāng)著我的面吹牛食零,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该抒,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼慌洪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼顶燕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起冈爹,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涌攻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后频伤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恳谎,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年憋肖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了因痛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岸更,死狀恐怖鸵膏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怎炊,我是刑警寧澤谭企,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站评肆,受9級(jí)特大地震影響债查,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓜挽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一盹廷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧久橙,春花似錦俄占、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吭敢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間暮芭,已是汗流浹背鹿驼。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辕宏,地道東北人畜晰。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瑞筐,于是被迫代替她去往敵國(guó)和親凄鼻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容