看看开源库Universal Image Loader中存在的Bug
379702597
8年前
<p>话说几年前,Universal Image Loader是安卓应用中图片加载的主流开源库,很多千万级的应用中都可以看到它的身影。前段时间抱着学习的态度,把Universal Image Loader的源码下载下来,准备领略一下国际大牛的风采,结果意外的发现了一个Bug,由于项目已经停止维护了,也就没有pull request。最近突然喜欢上了写博客,所以今天就把它的代码贴上了,和大家一起学习一下。(几年前应用搞不好就会OOM,会不会就是因为这个bug啊。不管是不是,大家先来看代码分析吧)</p> <p>这个bug是内存策略中存在的bug,会引起OOM。我们的分析会涉及以下四个类:</p> <p><img src="https://simg.open-open.com/show/cc38925b1ed12bbafd974e290ddaff7a.png"></p> <p>四个类的继承关系,请原谅我画的这么丑陋</p> <p>其中MemoryCache中,就五个方法,很简单:</p> <p><img src="https://simg.open-open.com/show/729eba94c542ee5f0dd3b10b99d3874c.png"></p> <p>MemoryCache.png</p> <p>在BaseMemoryCache中有成员变量:HashMap,它存储的值是Reference的Bitmap。其实问题就出在这里了,我们继续往下看。大家别着急,我文章最后会把四个类的全部代码贴上来,供大家仔细看。根据他的注释可以看出,要存储一个非强引用的对象,也就是弱引用或软引用。</p> <p><img src="https://simg.open-open.com/show/c8c6b3c21646307665d680cb5caec799.png"></p> <p>HashMap</p> <p>我们再来看看他的put方法,到了往HashMap里放了什么。根据下面代码可以看出是调用了createReference()这个抽象方法,交给子类去实现了。</p> <p><img src="https://simg.open-open.com/show/63d6e8230b5a87606a72532324e1bb0f.png"></p> <p>BaceMemoryCache的put方法</p> <p>BaseMemoryCache的直接子类LimitedMemoryCache也是一个抽象类,没有实现createReference()方法,在他的间接子类UsingFreqLimiteMemory中实现了.可以看出方法中创建了一个Bitmap的弱引用。弱引用就是只要发生GC,对象都有可能被回收。</p> <p><img src="https://simg.open-open.com/show/3ccd9a5431c1b79ed450bf1f4164f8d4.png"></p> <p>UsingFreqLimiteMemory的createReference()</p> <p>重点来啦,我们看UsingFreqLimitedMemory中的remove方法。它掉用的是父类中的get方法,由于直接父类LimitedMemoryCache中没有实现get方法,所以它调用的是BaseMemoryCache中的get方法。</p> <p><img src="https://simg.open-open.com/show/cf1c7122721f87528063d00965e2e0dc.png"></p> <p>UsingFreqLimitedMemory</p> <p>我们来看看BaseMemoryCache中的get方法:</p> <p><img src="https://simg.open-open.com/show/e1c893a3cff4da941e7cd25af807431a.png"></p> <p>BaseMemoryCache</p> <p>get方法就是通过成员变量HashMap获得BitMap。由于存储的值是一个弱引用,所以可能随时被GC。那么它子类就可能拿不到这个Bitmap的引用了,并且两个子类中都有HashMap保存着对应的Bitmap,所以子类中持有的Bitmap就永远无法移除了,直到程序发生OOM。</p> <p>下面就把全部代码贴上来,大家可以仔细研究一下,共四个类由父类到子类。欢迎大家在下面讨论交流。</p> <pre> <code class="language-java">/** * Interface for memory cache * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.9.2 */ public interface MemoryCache { /** * Puts value into cache by key * * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into * cache */ boolean put(String key, Bitmap value); /** Returns value by key. If there is no value for key then null will be returned. */ Bitmap get(String key); /** Removes item by key */ Bitmap remove(String key); /** Returns all keys of cache */ Collection<String> keys(); /** Remove all items from cache */ void clear(); }</code></pre> <pre> <code class="language-java">/** * Base memory cache. Implements common functionality for memory cache. Provides object references ( * {@linkplain Reference not strong}) storing. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.0.0 */ public abstract class BaseMemoryCache implements MemoryCache { /** Stores not strong references to objects */ private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>()); @Override public Bitmap get(String key) { Bitmap result = null; Reference<Bitmap> reference = softMap.get(key); if (reference != null) { result = reference.get(); } return result; } @Override public boolean put(String key, Bitmap value) { softMap.put(key, createReference(value)); return true; } @Override public Bitmap remove(String key) { Reference<Bitmap> bmpRef = softMap.remove(key); return bmpRef == null ? null : bmpRef.get(); } @Override public Collection<String> keys() { synchronized (softMap) { return new HashSet<String>(softMap.keySet()); } } @Override public void clear() { softMap.clear(); } /** Creates {@linkplain Reference not strong} reference of value */ protected abstract Reference<Bitmap> createReference(Bitmap value); }</code></pre> <pre> <code class="language-java">/** * Limited cache. Provides object storing. Size of all stored bitmaps will not to exceed size limit ( * {@link #getSizeLimit()}).<br /> * <br /> * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @see BaseMemoryCache * @since 1.0.0 */ public abstract class LimitedMemoryCache extends BaseMemoryCache { private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16; private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024; private final int sizeLimit; private final AtomicInteger cacheSize; /** * Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed * limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any * time) */ private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>()); /** @param sizeLimit Maximum size for cache (in bytes) */ public LimitedMemoryCache(int sizeLimit) { this.sizeLimit = sizeLimit; cacheSize = new AtomicInteger(); if (sizeLimit > MAX_NORMAL_CACHE_SIZE) { L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB); } } @Override public boolean put(String key, Bitmap value) { boolean putSuccessfully = false; // Try to add value to hard cache int valueSize = getSize(value); int sizeLimit = getSizeLimit(); int curCacheSize = cacheSize.get(); if (valueSize < sizeLimit) { while (curCacheSize + valueSize > sizeLimit) { Bitmap removedValue = removeNext(); if (hardCache.remove(removedValue)) { curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); } } hardCache.add(value); cacheSize.addAndGet(valueSize); putSuccessfully = true; } // Add value to soft cache super.put(key, value); return putSuccessfully; } @Override public Bitmap remove(String key) { Bitmap value = super.get(key); if (value != null) { if (hardCache.remove(value)) { cacheSize.addAndGet(-getSize(value)); } } return super.remove(key); } @Override public void clear() { hardCache.clear(); cacheSize.set(0); super.clear(); } protected int getSizeLimit() { return sizeLimit; } protected abstract int getSize(Bitmap value); protected abstract Bitmap removeNext(); }</code></pre> <pre> <code class="language-java">/** * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to * exceed size limit. When cache reaches limit size then the bitmap which used the least frequently is deleted from * cache.<br /> * <br /> * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.0.0 */ public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache { /** * Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache * size will exceed limit then object with the least frequently usage is deleted (but it continue exist at * {@link #softMap} and can be collected by GC at any time) */ private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>()); public UsingFreqLimitedMemoryCache(int sizeLimit) { super(sizeLimit); } @Override public boolean put(String key, Bitmap value) { if (super.put(key, value)) { usingCounts.put(value, 0); return true; } else { return false; } } @Override public Bitmap get(String key) { Bitmap value = super.get(key); // Increment usage count for value if value is contained in hardCahe if (value != null) { Integer usageCount = usingCounts.get(value); if (usageCount != null) { usingCounts.put(value, usageCount + 1); } } return value; } @Override public Bitmap remove(String key) { Bitmap value = super.get(key); if (value != null) { usingCounts.remove(value); } return super.remove(key); } @Override public void clear() { usingCounts.clear(); super.clear(); } @Override protected int getSize(Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override protected Bitmap removeNext() { Integer minUsageCount = null; Bitmap leastUsedValue = null; Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet(); synchronized (usingCounts) { for (Entry<Bitmap, Integer> entry : entries) { if (leastUsedValue == null) { leastUsedValue = entry.getKey(); minUsageCount = entry.getValue(); } else { Integer lastValueUsage = entry.getValue(); if (lastValueUsage < minUsageCount) { minUsageCount = lastValueUsage; leastUsedValue = entry.getKey(); } } } } usingCounts.remove(leastUsedValue); return leastUsedValue; } @Override protected Reference<Bitmap> createReference(Bitmap value) { return new WeakReference<Bitmap>(value); } }</code></pre> <p> </p> <p>来自:http://www.jianshu.com/p/e6f4e0ab57c3</p> <p> </p>