Android简单内存缓存
Earle96S
8年前
<h3><strong>一. 问题</strong></h3> <p>前段时间在公司项目中遇到一个问题,在集成百度地图时想要在地图上动态添加 ICON,这些 ICON 都是随时可变的,起初设计时,每次添加的 ICON 都是动态 new 出一个新的Bitmap,这样可以满足 ICON 随时可变的需求,但是需求完成之后,通过 AS 观察到 APP 内存飙升,APP 内存吃紧带来的坏处我就不再赘述了,为了解决这个问题,我就设计了一个简单的内存缓存框架来解决这个问题,有效的减少了 APP 的内存消耗。</p> <h3><strong>二. 解决</strong></h3> <p>解决问题时的问题思路我就不再说了,直接看下源码~</p> <pre> <code class="language-java">/** * Created by daiyiming on 2016/12/10. * 本地内存缓存 */ public final class MemoryCache<KO, VO> { public static final int CAPACITY_DEFAULT = 10; // 默认缓存 public static final int CAPACITY_INFINITY = -1; // 无限缓存 public static final int MAX_CAPACITY_DEFAULT = 20; // 默认最大缓存 public static final int MAX_CAPACITY_INFINITY = -1; // 无限最大缓存 private final LinkedList<ValueHolder<KO, VO>> mCacheList; // 缓存 private final HashSet<KO> mKeySet; // 键缓存 private volatile int mCapacity; // 容量 private volatile int mMaxCapacity; // 最大容量 private volatile boolean mAllowUpdate; // 键值冲突时是否准许更新 /** * 键值对组合类 * * @param <KI> 键类型 * @param <VI> 值类型 */ private static final class ValueHolder<KI, VI> { private final KI mKey; // 键不准许修改,若修改则用添加新的对象 private VI mValue; // 值可以修改,用于更新 private ValueHolder(KI key, VI value) { mKey = key; mValue = value; } /** * 更新值对象 * @param value 新的值对象 */ 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(); } } } /** * 值接口,实现帮助对象键值内存回收 */ 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:构造函数参数错误"); } 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)) { // 如果已经存在则复用对象 ListIterator<ValueHolder<KO, VO>> iterator = mCacheList.listIterator(); while (iterator.hasNext()) { ValueHolder<KO, VO> holder = iterator.next(); if (holder.mKey.equals(key)) { holder.update(value); // 如果不准许更新则删除重新添加 if (!mAllowUpdate && iterator.previousIndex() != 0) { iterator.remove(); mCacheList.addFirst(holder); } break; } } } else { // 不存在则添加 mKeySet.add(key); mCacheList.addFirst(new ValueHolder<>(key, value)); } // 如果大于最大容量且不是无限容量则清除末尾一个 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) { // 如果不是在头部就移动到头部 iterator.remove(); mCacheList.addFirst(holder); } // 删除一个超出容量的数据 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; } /** * 准许更新 * 决定添加过程中键值重复时直接原位置更新还是删除重新添加 * * @param allowUpdate 是否准许更新 */ public void allowUpdate(boolean allowUpdate) { mAllowUpdate = allowUpdate; } }</code></pre> <p>通过一个链表保存需要缓存的元素,每次添加的新元素放到链表的首部,添加时检测如果超出最大容量,则从链表的尾部删除一个元素,为什么这么做呢?主要是为了防止疯狂 put 元素导致 OOM。在每次get元素的时候,通过传入的 key 值遍历链表,当命中想要获取的元素的时候,将这个元素移到链表的首部,这样可以保证频繁获取的元素在链表靠前的位置,减少获取时间。在 get 成功的时候,如果链表长度大于容量(注意与最大容量的区别),我们就从列表末尾删除一个元素,因为末尾的元素表明这个元素很大的可能是长时间没人使用(get操作),即可视为过期元素。</p> <p>为什么要设置一个容量和最大容量的区别呢?因为考虑到可能存在疯狂 put 的操作,即用户一直在 put,如果最大容量大于容量,就给了最先 put 的元素有被重新拿到链表首部的可能性,即一定程度的加强了这个元素被再次利用的可能性,在之后的 get 的操作中,链表尾部元素逐渐被清除,链表长度逐渐回归正常。又因为设置了最大容量,给 put 操作设置了上限,所以基本不会有 OOM。</p> <p>当然防止有些时候需要缓存的时候 IO 连接,SOCKET 连接,Bitmap 等需要手动销毁的东西,我还实现了一个 Recycle 接口,如果缓存的键或者值需要用户自定义销毁操作,则实现这个接口即可。</p> <p>具体见下图</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/53b441fb52fda2c6f4c0a2c1eae15a4a.png"></p> <p style="text-align:center">put.png</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/d61128c37efff44a76f0f45404ca71f8.png"></p> <p style="text-align:center">get.png</p> <h3><strong>三. 使用</strong></h3> <pre> <code class="language-java">/** * Created by daiyiming on 2016/12/11. * 缓存执行类 */ 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(); } } } } }</code></pre> <p>这个实现是复杂实现,基本实现了所有功能,当然也可以简单使用~</p> <p> </p> <p>来自:http://www.jianshu.com/p/7bc6d216ff25</p> <p> </p>