在Glide开启内存缓存和Disk缓存的时候,加载同一个图片url,在第一次加载成功的前提下,第二次可能会从Disk中取,无法从内存缓存中取,按经验这个情况不可能发生,但实际上在项目中出现了,列表一滑动,就会出现闪跳,分析是因为开了一个线程从硬盘读取并解码来获取图片.但为什么不是从内存缓存中读取.需要深入Glide的缓存原理来分析.
Glide 内部的加载入口在Engile.load()方法
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = //EngineKey 貌似是一个缓存key
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); //从缓存加载的入口方法
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
重点是EngineKey ,这是一个缓存的key对象,暂不分析.这个结合后面的流程来分析
断点进入loadFromMemory()->loadFromCache()->getEngineResourceFromCache()
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
cache 的类型LruResourceCache,继承于LruCache,LruResourceCache并没有remove()方法,实际调用的是LruCache类的方法
public class LruCache<T, Y> {
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
private final long initialMaxSize;
private long maxSize;
private long currentSize;
.....
public synchronized Y remove(@NonNull T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
}
在remove方法中的cache类是LinkedHashMap,断点得知,T key,这个T的类型正好是EngineKey,我们知道LinkedHashMap或者HashMap寻址返回元素是和key的hashCode以及equals()方法有关,两个key的hashcode一样,equals返回true,才会认定是同一个key,并返回已经存在的value,那就接着看下EngineKey的hashCode()和equals()方法是怎么定义的.
class EngineKey implements Key {
private final Object model;
private final int width;
private final int height;
private final Class<?> resourceClass;
private final Class<?> transcodeClass;
private final Key signature;
private final Map<Class<?>, Transformation<?>> transformations;
private final Options options;
private int hashCode;
EngineKey(
Object model,
Key signature,
int width,
int height,
Map<Class<?>, Transformation<?>> transformations,
Class<?> resourceClass,
Class<?> transcodeClass,
Options options) {
this.model = Preconditions.checkNotNull(model);
this.signature = Preconditions.checkNotNull(signature, "Signature must not be null");
this.width = width;
this.height = height;
this.transformations = Preconditions.checkNotNull(transformations);
this.resourceClass =
Preconditions.checkNotNull(resourceClass, "Resource class must not be null");
this.transcodeClass =
Preconditions.checkNotNull(transcodeClass, "Transcode class must not be null");
this.options = Preconditions.checkNotNull(options);
}
@Override
public boolean equals(Object o) {
if (o instanceof EngineKey) {
EngineKey other = (EngineKey) o;
return model.equals(other.model)
&& signature.equals(other.signature)
&& height == other.height
&& width == other.width
&& transformations.equals(other.transformations)
&& resourceClass.equals(other.resourceClass)
&& transcodeClass.equals(other.transcodeClass)
&& options.equals(other.options);
}
return false;
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = model.hashCode();//1
hashCode = 31 * hashCode + signature.hashCode();//2
hashCode = 31 * hashCode + width;//3
hashCode = 31 * hashCode + height;//4
hashCode = 31 * hashCode + transformations.hashCode();//5
hashCode = 31 * hashCode + resourceClass.hashCode();//6
hashCode = 31 * hashCode + transcodeClass.hashCode();//7
hashCode = 31 * hashCode + options.hashCode();//8
}
return hashCode;
}
@Override
public String toString() {
return "EngineKey{"
+ "model="
+ model
+ ", width="
+ width
+ ", height="
+ height
+ ", resourceClass="
+ resourceClass
+ ", transcodeClass="
+ transcodeClass
+ ", signature="
+ signature
+ ", hashCode="
+ hashCode
+ ", transformations="
+ transformations
+ ", options="
+ options
+ '}';
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
throw new UnsupportedOperationException();
}
看到这里就大概明白了,之所以没有命中缓存,估计因为EngineKey返回的hashCode在即使url一样的情况下,最终计算得到的值也不一样.让我惊讶的是,影响EnigneKey的hashCode计算这么复杂,竟然有8个因素,url只是其中一个因子.有一个不一样,缓存就无法命中了.这只是推测,实际调试先记录第一次加载每一步的hashCode值.然后断点调试
发现在第二次执行
hashCode = 31 * hashCode + transformations.hashCode()
这句代码之后,hashCode变的和第一次的不一样,那问题就出在了transformations.hashCode()的计算上.后面几步的计算就不用看了,肯定不一样。transformations是什么对象呢,8个因子分别是什么呢.一一分下下,这8个因子分别是什么.
1.model?这个对象的来源是Glide.load(obj)传入的obj,可以是String类型,可以是Url类型,也可以是GlideUrl或者继承GlideUrl的类.
2.signature这个对象的来源是BaseRequestionOptions,RequestOptions的父类,默认类型是Emptysignature,这个加载图片来说不常设置,一般默认就是Emptysignature
3.width:view的宽度
4.height:view的高度
5.transformations的实际类型是:CachedHashCodeArrayMap,也是来自BaseRequestionOptions,存储的是transformation对象,比如圆角transformation或者圆形的transformation,这个来源路径的伪码如下:
Glide.with(activity).apply(RequestOptions().transform(GlideCircleTransformation()))
RequestOptins.transform(@NonNull Transformation<Bitmap> transformation, boolean isRequired)
@NonNull
T transform(@NonNull Transformation<Bitmap> transformation, boolean isRequired) {
if (isAutoCloneEnabled) {
return clone().transform(transformation, isRequired);
}
//可以看到外部传入的transformation,会被包装成DrawableTransformation,GifDrawableTransformation等.
DrawableTransformation drawableTransformation =
new DrawableTransformation(transformation, isRequired);
//之后transform()其实就是相当于往transformations这个对象put数据,一共4个,
//1 key:Bitmap value:transformation
//2 key:Drawable.class value:drawableTransformation
//3 key:BitmapDrawable value:drawableTransformation.asBitmapDrawable()
//4 key:GifDrawable value:GifDrawableTransformation()
transform(Bitmap.class, transformation, isRequired);
transform(Drawable.class, drawableTransformation, isRequired);
// TODO: remove BitmapDrawable decoder and this transformation.
// Registering as BitmapDrawable is simply an optimization to avoid some iteration and
// isAssignableFrom checks when obtaining the transformation later on. It can be removed without
// affecting the functionality.
transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);
transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);
return selfOrThrowIfLocked();
}
@NonNull
<Y> T transform(
@NonNull Class<Y> resourceClass,
@NonNull Transformation<Y> transformation,
boolean isRequired) {
if (isAutoCloneEnabled) {
return clone().transform(resourceClass, transformation, isRequired);
}
Preconditions.checkNotNull(resourceClass);
Preconditions.checkNotNull(transformation);
transformations.put(resourceClass, transformation);
fields |= TRANSFORMATION;
isTransformationAllowed = true;
fields |= TRANSFORMATION_ALLOWED;
// Always set to false here. Known scale only transformations will call this method and then
// set isScaleOnlyOrNoTransform to true immediately after.
isScaleOnlyOrNoTransform = false;
if (isRequired) {
fields |= TRANSFORMATION_REQUIRED;
isTransformationRequired = true;
}
return selfOrThrowIfLocked();
}
??transformations这个map的数据,一共4个, ? ?//1 key:Bitmap.class value:transformation(我们传入的transformation) ? ?//2 key:Drawable.class value:drawableTransformation(包装了transformation) ? ?//3 key:BitmapDrawable.class value:drawableTransformation.asBitmapDrawable() ? ?//4 key:GifDrawable.class value:GifDrawableTransformation()?(包装了transformation)
然后transformations.hashCode()的方法如下
//CachedHashCodeArrayMap
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = super.hashCode();
}
return hashCode;
}
//SimpleArrayMap
@Override
public int hashCode() {
final int[] hashes = mHashes;
final Object[] array = mArray;
int result = 0;
for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
Object value = array[v];
result += hashes[i] ^ (value == null ? 0 : value.hashCode());
}
return result;
}
计算方法遍历map,将key的hash和value的hash进行按位与得出的值累加(为什么hashes数组的元素是key的hash值,这个可以去看ArrayMap的原理),得到最终的hashcode,我们分析了这个map有4条数据,key其实是Class类型的对象,所以每个类型在内存中有唯一值,影响map的hashcode的值是value的hash,分析下这4个value
1.?value:transformation,我们传入的transformation,以GlideCircleTransformation为例,就是GlideCircleTransformation对象的hashCode,如果没重写,则返回系统分配的hashcode值
2.value:drawableTransformation,
@Override
public int hashCode() {
return wrapped.hashCode();
}
wapped就是我们的transformation,返回的也是transformation的hashcode值
3 value:drawableTransformation.asBitmapDrawable()
public Transformation<BitmapDrawable> asBitmapDrawable() {
return (Transformation<BitmapDrawable>) (Transformation<?>) this;
}
this指代的还是drawableTransformation本身,所以返回的hashcode也是transformation的hashcode值
4? value:GifDrawableTransformation(),
.
@Override
public int hashCode() {
return wrapped.hashCode();
}
wapped也是我们传入的transformation,返回的也是transformation的hashcode值
综上分析,所以transformations这个map的hashcode取决于我们传入的transformation的hashcode值.
6.分析了第5个因子,接着分析第6个因子reourceClass,它的来源也是BaseRequestionOptions,默认是Object.class
7.transcodeClass也是个Class的对象,来自于Glide.as***方法,可以是Bitmap,Drawable,File等类型
8.options来自于BaseRequestionOptions的,需要调用set方法设置.
Glide.with(activity).asDrawable().apply(RequestOptions().set("EXTRA", extra)
//BaseRequestOptions
???????@NonNull
@CheckResult
public <Y> T set(@NonNull Option<Y> option, @NonNull Y value) {
if (isAutoCloneEnabled) {
return clone().set(option, value);
}
Preconditions.checkNotNull(option);
Preconditions.checkNotNull(value);
options.set(option, value);
return selfOrThrowIfLocked();
}
options的类型Options,这个对象的hashcode值取决于value的hashcode,按照给出的案例就是取决于extra对象的hashcode值
@Override
public int hashCode() {
return values.hashCode();
}
分析了8个因子,我们总结一下可变因子是7个分别是model,widht,height,transformation,transcodeClass,options,.另外一个signature,可以忽略.
如果对同一个url,而且对Glide加载代码一样的的话,影响的就只有transformation,options.
所以主要对同一个url加载,而且Glide加载代码一致,就是关注transformation,options,这两个因子都是和对应对象返回的hashCode方法有关。那么有两种情况.其中一种假如我们设置transformation,options的value,并没有实现hashcode方法,那么系统就根据对象分配的hashcode来指定.每次加载因为分配的对象是重新构造的,所以EngineKey的hashcode的值不一样,导致一个现象:加载同一个url,EngineKey返回的hashCode并不一致,缓存无法命中,只能从硬盘缓存中取,硬盘根据图片的地址来缓存,没有那么复杂的key的计算.另外一种,如果我们实现了hashcode方法,保证每次加载两个因子的hashcode值一样,那么同一个url,EngineKey返回的hashCode也一样,缓存就能命中了.这样就回答了开头提出的问题.解决方案就是重写transformation,options.value各自类的hashCode方法.比如
GlideRoundTransformation{
private static final String ID = "com.videogo.glide.transformation.GlideRoundTransformation";
...
@Override
public boolean equals(Object o) {
return o instanceof GlideRoundTransformation;
}
public int hashCode() {
return ID.hashCode();
}
}
|