老衲上个月在清理项目历史遗留下的疑难问题的时候,被分配了这么了个诡异的bug,当快速进入退出我们项目的时候,如果执行的次数量级够大,则会出现
Glide : trying to use a recycled bitmap balabala......
奇怪的是,其他同事在图片的使用时,已经按照Glide的要求在各个地方去释放内存了。如下
Glide.get(this).clearMemory();
但是,该问题依然如跗骨之蛆一般存在。没法办,只好去撸源码找原因。
clearMemory()方法的内部实现逻辑是
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
// memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
memoryCache.clearMemory();
bitmapPool.clearMemory();
arrayPool.clearMemory();
刚开始的思路是,LruCache内的清理逻辑出了问题。先看下它内部的实现逻辑
Map.Entry last;
Iterator> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove);
}
Lru本质就是个LinkedHashMap,本质就是hashmap的对象清理逻辑,瞅了一顿也没发现有啥不对。于是把目标转移到了Glide的精髓BitmapPool
Glide的一个最重要的思想就是对象池的使用,占内存一样的两个bitmap会进行内存的复用,因为首页图片的尺寸和格式都是同样的,这里肯定会进行bitmap的复用。所以接下来重点BitmapPool的实现逻辑。
BitmapPool有三个实现类
不解释,直接看LruBitmapPool的clearMemory方法
@Override
public void clearMemory() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "clearMemory");
}
trimToSize(0);
}
直接找trimToSize(0)
private synchronized void trimToSize(long size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
// 划重点!!!!!
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}
重点就在于 tracker.remove(removed)这句代码, tracker???这特么是个毛?继续跟
private interface BitmapTracker {
void add(Bitmap bitmap);
void remove(Bitmap bitmap);
}
这也简单,其实就是一个专门用于添加和删除bitmap的一个工具类,重点来了。。看下LruBitmapPool构造方法里BitmapTracker的实现类:
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}
继续跟NullBitmapTracker:
private static final class NullBitmapTracker implements BitmapTracker {
@Synthetic
NullBitmapTracker() { }
@Override
public void add(Bitmap bitmap) {
// Do nothing.
}
@Override
public void remove(Bitmap bitmap) {
// Do nothing.
}
}
Do nothing...
noting...
...
Emmm.....我特么....
所以可能的原因是bitmap依然在BitmapPool中,但是已经被recycled了,导致下次使用了复用的bitmap。
解决思路也简单,禁用掉BitmapPool对象池,或者给LruBitmapPool自己设置一个自定义Tracker的实现类。
对于常用的开源框架,还是建议同行们多了解下内部的实现逻辑,关键时候派得上用场。