缓存
使用到内存缓存,磁盘缓存。
内存缓存:防止应用重复将图片读入到内存,造成内存资源浪费。
磁盘缓存:防止应用重复的从网络或者其他地方下载和读取数据。
缓存key
缓存源码在Engine.load
EngineKey中重写equals和hashcode方法,这样就能确保只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象。
内存缓存
默认内存缓存是开启的,禁用内存缓存只需要调用skipMemoryCache
传true即可。
缓存原理:弱引用机制和LruCache算法。
public synchronized LoadStatus load(...) {
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// ActiveResources表示当前正在活动中的资源
// 内部采用弱引用的HashMap来缓存活动资源
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;
}
//检查内存的Lrucache缓存是否有目标图片
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;
}
...
}
弱引用机制
获取
@Nullable
private EngineResource> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
// 从弱引用HashMap中获取数据,存储的图片是转换后的图片
EngineResource> active = activeResources.get(key);
if (active != null) {
//将EngineResource中的引用计数加1,这个变量用来记录图片被引用的次数
active.acquire();
}
return active;
}
@Nullable
synchronized EngineResource> get(Key key) {
// 从HashMap中获取弱引用对象
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
// 从弱引用中获取活动资源
EngineResource> active = activeRef.get();
if (active == null) {
//清理弱引用缓存,并保存到LruCache缓存中
cleanupActiveReference(activeRef);
}
return active;
}
首先会从弱引用集合获取活动资源,图片资源其实就是这个弱引用对象关联的对象,能获取到直接返回。当弱引用缓存所关联的图片资源被回收时,会将图片资源保存到LruCache缓存中,从而保证了当获取不到弱引用缓存的图片时,可以获取的到该图片的LruCache缓存。
存储
主要体现在两个方面:在主线程展示图片前,获取LruCache缓存时。
2.1在主线程展示图片前
EngineJob#notifyCallbacksOfResult
void notifyCallbacksOfResult() {
ResourceCallbacksAndExecutors copy;
Key localKey;
EngineResource> localResource;
...
//内部缓存存储的入口
//实际上调用的是Engine的onEngineJobComplete
listener.onEngineJobComplete(this, localKey, localResource);
for (final ResourceCallbackAndExecutor entry : copy) {
//回到主线程展示照片
entry.executor.execute(new CallResourceReady(entry.cb));
}
//通知上层删除弱引用缓存数据
decrementPendingCallbacks();
}
ActiveResources#activate
synchronized void activate(Key key, EngineResource> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
//key值不重复返回null,key值重复返回旧对象
if (removed != null) {
//如果key值重复,就将之前的弱引用对象的图片资源置为null
removed.reset();
}
}
正在使用的图片会存储到弱引用缓存中而不是LruCache缓存。
2.2获取LruCache缓存时
获取LruCache缓存时存储
删除
3.1 JVM GC
当JVM进行GC时,由于弱引用对象的特性,导致了弱引用缓存所关联的对象也会被回收,然后就会删除掉这个弱引用缓存对象。
3.2 引用计数为0
在release方法中引用计数减1,当引用计数减到0时,首先将活动资源从弱引用的HashMap中移除,然后将它缓存到LruCache缓存中。
LruCache缓存
获取
private EngineResource> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//获取图片缓存,并将该缓存从LruCache缓存中删除
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
//将EngineResource的引用计数加1
cached.acquire();
//将内存缓存存入弱引用缓存中
//好处:保护这些图片不会被LruCache算法回收掉
activeResources.activate(key, cached);
}
return cached;
}
存储
当弱引用缓存删除时,会将缓存存储到LruCache缓存中。
删除
当获取LruCache缓存的同时对该LruCache缓存进行删除操作。
磁盘缓存
默认磁盘缓存是开启的,禁用磁盘缓存只需要调用diskCacheStrategy
传DiskCacheStrategy.NONE
即可。
缓存原理:使用Glide自定义的DiskLruCache算法。
最大缓存空间:250 MB,缓存位置"image_manager_disk_cache"。
五种缓存策略:
1,DiskCacheStrategy.NONE// 表示不缓存任何内容
2,DiskCacheStrategy.DATA// 表示只缓存原始图片
3,DiskCacheStrategy.RESOURCE// 表示只缓存转换过后的图片
4,DiskCacheStrategy.ALL // 表示既缓存原始图片,也缓存转换过后的图片
5,DiskCacheStrategy.AUTOMATIC//表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)
通过之前源码分析可知,当内存没有缓存时,就会开启新线程加载图片。接下来就执行DecodeJob.run
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//获取任务场景
stage = getNextStage(Stage.INITIALIZE);
//获取这个场景的执行者
currentGenerator = getNextGenerator();
//执行者执行任务
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
//获取转换后图片的执行者
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
//获取原始图片的执行者
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// 无缓存, 网络获取数据的执行者
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
RESOURCE缓存
获取
Engine#runGenerators -> ResourceCacheGenerator#startNext
public boolean startNext() {
...
while (modelLoaders == null || !hasNextModelLoader()) {
...
Key sourceId = sourceIds.get(sourceIdIndex);
Class> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation> transformation = helper.getTransformation(resourceClass);
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//从磁盘缓存中获取缓存信息进行处理
//首先通过getDiskCache获取DiskCache对象
//然后通过key获取到转换过后的资源
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
//该modeLoaders的类型为File类型的
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//获取数据加载器
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
//构建一个加载器,构建出来的是ByteBufferFileLoader
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
//调用了ByteBufferFileLoader的内部类ByteBufferFetcher的loadData
//最后会把结果回调给DecodeJob的onDataFetcherReady
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
ByteBufferFileLoader#loadData
public void loadData(@NonNull Priority priority,
@NonNull DataCallback super ByteBuffer> callback) {
ByteBuffer result;
try {
//获取对应磁盘上的转换过后的数据
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
}
callback.onLoadFailed(e);
return;
}
callback.onDataReady(result);
}
在loadData中会通过ByteBufferUtil工具类来获取对应磁盘文件的数据,然后通过回调ResourceCacheGenerator的onDataReady方法将数据回调出去。在onDataReady方法中就会对数据进行压缩转换等操作,然后进行展示。
存储
其存储位置应该是在对原始图片压缩转换解析等一系列操作完后进行的。
DecodeJob#decodeFromRetrievedData
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource resource = null;
try {
//转换后的图片资源
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
//通知外界资源获取成功
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
.....
//图片加载流程时重点关注的地方
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
//是否可以将转换的图片缓存
if (deferredEncodeManager.hasResourceToEncode()) {
//磁盘缓存入口
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
onEncodeComplete();
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
//将图片资源缓存到资源磁盘
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
删除
- 手动调用Glide.get(applicationContext).clearDiskCache()
- 内存管理中直接删除文件
DATA缓存
获取
DataCacheGenerator#startNext
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
...
Key sourceId = cacheKeys.get(sourceIdIndex);
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
//获取缓存key的磁盘资源
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
存储
从网络中加载到后直接进行磁盘缓存。
HttpUrlFetcher#loadData
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//获取网络图片的输入流
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//将inputStream回调出去,回调了SourceGenerator的onDataReady
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
SourceGenerator#onDataReady
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//如果开启了磁盘缓存
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//将网络获取到的原始数据,赋值给dataToCache
dataToCache = data;
//调用DecodeJob的reschedule,用线程池执行任务,实际上就是再次调用SourceGenerator的startNext
cb.reschedule();
} else {
//没有开启磁盘缓存
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
DecodeJob#reschedule -> EngineJob#reschedule(再次切换到网络线程,执行DecodeJob的run方法) -> SourceGenerator#startNext
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//将原始图片放入缓存
//构造一个DataCacheGenerator对象,用来加载刚保存的磁盘缓存
cacheData(data);
}
//当原始图片放入磁盘缓存后,sourceCacheGenerator为DataCacheGenerator
//然后继续执行DataCacheGenerator的startNext方法
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
//走到这,说明是没有开启磁盘缓存或获取不到磁盘缓存的情况下
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
删除
同RESOURCE缓存一样。
内存泄漏
图片正在获取中,如果突然关闭页面,这个页面会造成内存泄漏吗?
不会,Glide内部有处理,保存全局的都会自动获取applicationContext,实际全部传入Application的context会造成内存问题。
如何设计一个大图加载框架
主要要注意以下几个部分:
1.线程切换
子线程进行下载,解码,缓存。主线程用来界面展示图片。
2.缓存策略
防止应用重复将图片读入到内存,造成内存资源浪费。防止应用重复的从网络或者其他地方下载和读取数据。
3.图片加载
需要防止OOM,要进行图片裁剪,小图压缩来减少占用内存,更改像素格式减少占用内存。
4.避免内存泄漏
跟随着控件的生命周期,当控件销毁时结束下载任务,进行资源释放。
Glide如何加载gif图
ImageView不支持加载Gif动图,ImageView 本身就是一个View,View的绘制需要用Canvas,而Canvas只支持canvas.drawBitmap,也就是同一时间只能绘制一张位图,而Gif是由多帧图片组成。
Glide加载Gif的原理比较简单,就是将Gif解码成多张图片进行无限轮播,每帧切换都是一次图片加载请求,再加载到新的一帧数据之后会对旧的一帧数据进行清除,然后再继续下一帧数据的加载请求,以此类推,使用Handler发送消息实现循环播放。
具体源码流程入口:GifDrawable#startFromFirstFrame
各个图片加载框架进行比较
Glide和Picasso对比:
- 内存使用
Glide默认使用RGB_565格式的Bitmap
Picasso默认使用ARGB_8888格式的Bitmap;
RGB_565比ARGB_8888格式的占用内存小一半。 - 图片格式支持
Glide可以加载GIF动态图,而Picasso不能。 - 缓存策略
Picasso缓存的是没有经过裁剪的原图,而Glide缓存可以缓存裁剪之后的图片,所以Glide占的内存可能要小一些。 - 库的大小
Glide > Picasso - 配置
Glide 提供了非常多的配置,你可以非常灵活的根据你的需求来客制化。
Picasso 提供的可配置相对就少了很多。
Fresco和Glide对比:
- layout使用
Glide可以用原生ImageView,Fresco要用特有layout,SimpleDraweeView。 - 缓存策略
Fresco缓存的是全尺寸的,而Glide缓存可以缓存裁剪之后的图片。 - 内存使用
Glide的图片质量为RGB565,显示质量一般但体积较小,而Fresco的图片质量为ARGB8888。
Fresco图片显示质量会更高,占用内存更大。 - 加载进度
Fresco有进度,Glide没有进度。 - 缓存
Glide: 内存和磁盘缓存
Fresco:三级缓存,分别是Bitmap缓存,未解码图片缓存, 文件缓存 - 库的大小
Fresco > Gradle
参考
源码解析:Glide 4.9之缓存策略
知道了这些内容,闭着眼面试Glide!
Glide三部曲之Gif加载原理