真正的傻瓜式,人人都看得懂
glide一共有几级缓存
三级缓存,activeResource,内存缓存,硬盘缓存
有代码为证:class Engine
//先从activeResource加载
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//有结果就直接返回
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//从内存缓存中加载
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//都没有,到异步线程去执行逻辑,包括硬盘缓存跟请求网络
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
activeResource
比如如下的页面,两个imageview加载同样的一张图片,在内存中,其实只有一份bitmap,这个就是activeResource的功劳
通过glide加载的图片资源,多个地方加载同个资源,都是返回同一份bitmap,避免bitmap重复(当页面销毁,资源也会从activeResource中移除)
ActiveResources
代码参考
//加入图片的引用,内部是一个hashmap的弱引用在管理
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
//移除引用
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
//查询是否有某个图片的引用
@Nullable
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
所以,内部的资源加载(各种R.drawable),图片加载,尽量使用glide来加载,是最好的选择
那有个问题来了,如果加载两张一样的图片,但是imageview的宽高不一样,还是内存中只有一份bitmap吗?
我们直接验证看下
可以看到,内存有两张bitmap,并且两张bitmap的尺寸不一样
这里就涉及到上面保存引用的key的逻辑了,可以直接看代码
class EngineKeyFactory {
@SuppressWarnings("rawtypes")
EngineKey buildKey(Object model, Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
}
可以看到,key跟很多参数相关
model:就是加载的url
signature:额外的签名
width,height:imageview的宽高
transformations:就是转换,比如centerCrop,roundCorner等
由于我们这里,只是宽高不一样,所以为了保证同一个key,保证宽高是一样的就好,可以使用override方式
Glide.with(this).load(url).override(Target.SIZE_ORIGINAL).into(imageView)
这样key是一样,在内存中也就只有一份bitmap了,提升了App性能
内存缓存
内存缓存的大小
有一套复杂的计算规则,不过大多数手机,都是屏幕宽高*8,常规的手机就是17M左右
详细的计算规则可以查看类MemorySizeCalculator
对于大多数App,这个内存是偏小的,通过直接放大1.5倍,代码如下
Glide.get(context).setMemoryCategory(MemoryCategory.HIGH);
如果还是不够的话,需要通过AppGlideModule
来实现
内存缓存内部是采用LinkedHashMap来实现的,最新加的,放在最后面,缓存总大小到了上限值,越早加入缓存的,越先被清空,可以看下构造函数
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
关键是这个accessOrder
,需要设置为true,这样就会按照范围的顺序排序,最近被访问或者使用的,就越不会被回收
获取一个内存缓存,很简单,就是从hashmap的get方法就可以
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
@Nullable
public synchronized Y get(@NonNull T key) {
return cache.get(key);
}
放入一个图片到缓存中
/**
* Adds the given item to the cache with the given key and returns any previous entry for the
* given key that may have already been in the cache.
*
* <p>If the size of the item is larger than the total cache size, the item will not be added to
* the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
* the given key and item.
*
* @param key The key to add the item at.
* @param item The item to add.
*/
@Nullable
public synchronized Y put(@NonNull T key, @Nullable Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
if (item != null) {
currentSize += itemSize;
}
@Nullable final Y old = cache.put(key, item);
if (old != null) {
currentSize -= getSize(old);
if (!old.equals(item)) {
onItemEvicted(key, old);
}
}
evict();
return old;
}
放入内存缓存的时机是什么时候??
页面销毁的时候,证据如下
逻辑是这样
加载到一张图片,显示在view上,同时放入activeResource缓存,单view销毁了,从activeResource缓存中挪到memoryCache中
下次再打开页面,加载同一种图片,从memoryCache中,remove掉该图片,加载到view里面,同时放入activeResource中
所以放入内存缓存中的图片,都是不在页面显示的图片
硬盘缓存
硬盘缓存的大小和默认缓存路径
大小:250M
路径:/data/user/0/com.example.myapplication/cache/image_manager_disk_cache
interface Factory {
/** 250 MB of cache. */
int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
/** Returns a new disk cache, or {@code null} if no disk cache could be created. */
@Nullable
DiskCache build();
}
问题:正写入一个缓存文件到硬盘,App崩溃了,怎么办?
答案:日志文件
一个日志文件,内容大概这样
* libcore.io.DiskLruCache
* 1
* 100
* 1
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
写入一个缓存的逻辑是这样
现在日志文件,写一行DIRTY的日志
写入真实的缓存的图片
在日志文件,写入一行CLEAN的日志
这样,当写入的图片失败,或者App崩溃,日志里面,就只有DIRTH记录,没有CLEAN记录,就可以把这次DIRTH的缓存给删掉
加载一个缓存文件
通过日志文件,会生成一个硬盘缓存的hashmap,通过key索引,可以加载到这个拿到这个hashmap里面的值,可以实际拿到文件的缓存路径
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
删除一个缓存
先删除文件,然后往日志里面写入REMOVE的标记
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
删除所有的硬盘缓存
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
日志文件路径:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/journal
缓存文件路径:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/6da652fb74c5ae6035ec3f7cab315b6f5dedb11515c173b2097d02a14c7974b9.0