Glide分析
一、Glide用法及介绍
1.1 什么是Glide?
Glide是一个加载图片的库,作者是bumptech,它是在泰国举行的google 开发者论坛上google为我
们介绍的,这个库被广泛的运用在google的开源项目中。
1.2 Glide解决什么问题?
Glide是一个非常成熟的图片加载库,他可以从多个源加载图片,如:网路,本地,Uri等,更重要的
是他内部封装了非常好的缓存机制并且在处理图片的时候能保持一个低的内存消耗。
1. 3 Glide的优缺点
1.3.1 优点
1.多样化媒体加载
Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video
2. 生命周期集成
通过设置绑定生命周期,我们可以更加高效的使用Glide提供的方式进行绑定,这样可以更好的让
加载图片的请求的生命周期动态管理起来
**3.**高效的缓存策略
A. 支持Memory和Disk图片缓存
B. Picasso 只会缓存原始尺寸的图片,而 Glide 缓存的是多种规格,也就意味着 Glide 会根据你
ImageView 的大小来缓存相应大小的图片尺寸
比如你 ImageView 大小是200200,原图是 400400 ,而使用 Glide 就会缓存 200200 规格的图,
而 Picasso 只会缓存 400400 规格的。这个改进就会导致 Glide 比 Picasso 加载的速度要快,毕竟少了
每次裁剪重新渲染的过程,非常灵活 & 加载速度快
C. 内存开销小
默认的 Bitmap 格式是 RGB_565 格式,而 Picasso 默认的是 ARGB_8888 格式,这个内存开销要小一
半。
Android关于图片内存计算,共有四种,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存(默认,色彩最细腻=显示质量最高=占用的内存也最大)
RGB_565:每个像素占用2byte内存(8bit = 1byte)
举例说明:一个32位的PNG=ARGB_8888=1204x1024,那么占用空间是:1024x1024x(32/8) =
4,194,304kb=4M左右
在解析图片的时候,为了避免oom和节省内存,最好使用ARGB_4444模式(节省一半的内存空间)
1.3.2缺点
1.使用方法复杂
Glide功能强大,但代码量大、流转复杂。在较深掌握的情况下才推荐使用,免得出了问题难以下
手解决。
1.4 Glide与其他第三方库的比较
1.5 Glide用法
1.5.1首先添加依赖
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
1.5.2 Glide的一个完整的请求至少需要三个参数
代码如下:
ImageView imageView = findViewById(R.id.imageView);
Glide.with(context)
.load(url)
.into(imageView);
with(Context context) - 需要上下文,这里还可以使用 Activity、FragmentActivity、
android.support.v4.app.Fragment、android.app.Fragment 的对象。将 Activity/Fragment 对象作为
参数的好处是,图片的加载会和 Activity/Fragment 的生命周期保持一致,例如:onPaused 时暂停加
载,onResume 时又会自动重新加载。所以在传参的时候建议使用 Activity/Fragment 对象,而不是
Context.
load(String url) - 这里我们所使用的一个字符串形式的网络图片的 URL,后面会讲解 load() 的更多使用方式.
.into(imageView);into(ImageView imageView) - 你需要显示图片的目标 ImageView.
1.5.3 占位图设置
偶尔出现图片加载慢或者加载不出来的情况是难以避免的,所以为了 UI 能好看一些,我们会使用占
位图。Glide 也为我们提供这种方法 placeHolder() 和 error()
ImageView imageView = (ImageView) findViewById(R.id.imageView);
Glide.with(context)
.load(url)
.placeholder(R.drawable.place_image)图片加载出来前,显示的图片
.error(R.drawable.error_image)//图片加载失败后,显示的图片
.into(imageView);
**注:这里需要注意一点,**placeholder() 和 error() 的参数都是只支持 int 和 Drawable 类型的参
数,这种设计应该是考虑到使用本地图片比网络图片更加合适做占位图。
1.5.4 缩略图
Glide.with(context)
.load(url)
.thumbnail(0.2f)
.into(imageView);
调用 thumbnail() 方法,参数是 float 类型,作为其倍数大小。例如,你传入 0.2f 作为参数,Glide
将会显示原始图片的20%的大小,使用 thumbnail() 方法来设置是简单粗暴的,但是如果缩略图需要通过网络加载相同的全尺寸图片,就不会很快的显示了。所以 Glide 提供了另一种防止去加载缩略图,先看
代码
private void loadImageThumbnailRequest(){
// setup Glide request without the into() method
DrawableRequestBuilder<String> thumbnailRequest = Glide.with( context ).load( url );
// pass the request as a a parameter to the thumbnail request
Glide.with( context )
.load( url )
.thumbnail( thumbnailRequest )
.into( imageView );
}
与第一种方式不同的是,这里的第一个缩略图请求是完全独立于第二个原始请求的。该缩略图可以是不同的资源图片,同时也可以对缩略图做不同的转换,等等…
1.5.5 动画开关
动画效果可以让图片加载变得更加的平滑,crossFade() 方法强制开启 Glide 默认的图片淡出淡入动画,当前版本3.7.0是默认开启的。crossFade() 还有一个重载方法 crossFade(int duration)。可以控制动画的持续时间,单位ms。动画默认的持续时间是300ms。既然可以添加动画,那肯定就可以设置没有任何淡出淡入效果,调用 dontAnimate()
Glide.with(context)
.load(url)
.crossFade()//或者使用 dontAnimate() 关闭动画
.placeholder(R.drawable.place_image)
.error(R.drawable.error_image)
.into(imageView);
1.5.6 图片大小与裁剪
在项目开发过程中,指定图片显示大小长长可能用到,毕竟从服务器获取的图片不一定都是符合设计
图的标准的。我们在这里就可以使用 override(width,height) 方法,在图片显示到 ImageView 之前,
重新改变图片大小。
Glide.with(context)
.load(url)
.override(width,height)//这里的单位是px
.into(imageView);
在设置图片到 ImageView 的时候,为了避免图片被挤压失真,ImageView 本身提供了 ScaleType 属性,这个属性可以控制图片显示时的方式,具体的属性使用还是去搜索吧!Glide 也提供了两个类似的方法 CenterCrop() 和 FitCenter(),CenterCrop() 方法是将图片按比例缩放到足矣填充 ImageView 的尺寸,但是图片可能会显示不完整;而 FitCenter() 则是图片缩放到小于等于 ImageView 的尺寸,这样图片是显示完整了,但是 ImageView 就可能不会填满了。
1.5.7 图片的缓存处理
为了更快的加载图片,我们肯定希望可以直接拿到图片,而不是进行网络请求,所以我们需要缓存。Glide 通过使用默认的内存和磁盘缓存来避免不必要的网络请求,之后我们再详细的去看它的实现
1.内存缓存
内存缓存是 Glide 默认帮我们做了的,除非你不需要,可以调用 skipMemoryCache(true) 告诉 Glide跳过内存缓存。这样 Glide 就不会把这张图片放到内存缓存中,该方法只影响内存缓存。(不要问调用skipMemoryCache(false)的问题,Glide 是默认将图片放入内存缓存中的)
2.磁盘缓存
磁盘缓存也是默认开启的,当然也是可以关闭的,不过关闭的方式略微有点不一样。
Glide.with(context)
.load(url)
.skipMemoryCache(true)
.diskCacheStrategy( DiskCacheStrategy.NONE )
.into(imageView);
2.1自定义磁盘缓存行为
使用 DiskCacheStrategy 可以为 Glide 配置磁盘缓存行为。Glide 的磁盘缓存比较复杂,这也是在图片加载可以比 Picasso 的原因(之一)。Picasso 只缓存了全尺寸的图片,而 Glide 的不同之处在于,Glide 不仅缓存了全尺寸的图,还会根据 ImageView 大小所生成的图也会缓存起来。比如,请求一个 800x600 的图加载到一个 400x300 的 ImageView 中,Glide默认会将这原图还有加载到 ImageView 中的 400x300 的图也会缓存起来。
DiskCacheStrategy 的枚举意义:
DiskCacheStrategy.NONE 什么都不缓存
DiskCacheStrategy.SOURCE 只缓存全尺寸图
DiskCacheStrategy.RESULT 只缓存最终的加载图
DiskCacheStrategy.ALL 缓存所有版本图(默认行为)
Glide.with(context)
.load(url)
.diskCacheStrategy( DiskCacheStrategy.SOURCE )
.into(imageView);
1.5.8 图片优先级
同一时间加载多个图片,App 将难以避免这种情况。如果这个时候我们希望用户的体验更好,往往会
选择先加载对于用户更加重要的图片。Glide 可以调用 .priority() 方法配合 Priority 枚举来设置图片加
载的优先级。
//设置 HIGH 优先级
Glide.with( context )
.load( highPriorityImageUrl )
.priority (Priority.HIGH )
.into( imageView );
//设置 LOW 优先级
Glide.with( context )
.load( lowPriorityImageUrl )
.priority( Priority.LOW )
.into( imageView );
Priority.LOW
Priority.NORMAL
Priority.HIGH
Priority.IMMEDIAT
//这里有一点需要注意,优先级并不是完全严格遵守的。Glide 将会用他们作为一个准则,尽可能的处理这 些请求,但是不能保证所有的图片都会按照所有要求的顺序加载。
1.5.9 显示 Gif 和 Video
显示 GIf 对于 Glide 来说一个比较特别的功能(至少 Picasso 暂时还不行)而且使用起来非常简单
Glide.with( context )
.load( gifUrl )
.placeholder( R.drawable.default )
.error( R.drawable.error )
.into( imageView );
这段代码还有点问题,如果加载的不是一张 gif 图的话,是没有办法显示的。
Glide.with( context )
.load( gifUrl )
.asGif()
.error( R.drawable.error )
.into( imageView );
做以上修改,如果图片类型不是 Gif 图的话就会当作 load 失败来处理,因此 error() 会被回调。即使这个url的图片是好的,也是不会显示的。当然,如果你想显示 Gif 但只是向现实静态的图片你就可以这么做
Glide.with( context )
.load( gifUrl )
.asBitmap()
.error( R.drawable.error )
.into( imageView );
仅仅是显示 Gif 的第一帧图像,这样就可以保证图片的正常显示了。
还有一个神奇的功能,Glide 还能显示视频!But…只能够显示手机本地的视频,要是向现实网络上的视频的话,还是另寻他法吧!
Glide.with( context )
.load( Uri.fromFile( new File( filePath ) ) )
.into( imageView );
二、Glide源码分析
2.1 Glide.with().load().into() 流程分析
2.2 相关类源码分析
2.2.1 基本用法
with()
创建一个加载图片的实例
接收Context,Activity或Fragment参数,也
可以为ApplicationContext
与生命周期有关
load()
用于指定待加载的图片资源
Glide.with( context )
.load( gifUrl )
.asBitmap()
.error( R.drawable.error )
.into( imageView );
String filePath = “/storrage/emulated/0/Pictures/video.mp4”;
Glide.with( context )
.load( Uri.fromFile( new File( filePath ) ) )
.into( imageView );包括网络图片,本地图片,uri等
多个方法重载
into()
希望图片显示在哪个ImageView上
placeholder()
占位图
diskCacheStrategy()
硬盘缓存策略
error()
异常占位图
asGif() || asBitmap
只允许gif或静态图
override()
指定图片大小
2.2.2 源码分析
Gilde.with(this).load(url).into(imageView)…
略.
2.2.3 Glide****的异步加载机制
采用三个线程池:
加载源文件的线程池,包括网络加载
加载硬盘缓存的线程池
动画线程池
2.2.4 线程切换策略-Handler**
Glide使用andriod提供的Handler 和 Looper 来满足线程间的通信。
Handler先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。
1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。 2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收 Looper从Message Queue取出)所送来的消息。
3)Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
2.3 缓存机制
2.3.1 缓存机制总览图
2.3.2 缓存机制流程图
2.3.3 Glide****缓存功能相关用法
设置内存缓存开关:
skipMemoryCache(true)
设置磁盘缓存模式:
diskCacheStrategy(DiskCacheStrategy.NONE)
可以设置4种模式:
- DiskCacheStrategy.NONE:表示不缓存任何内容。
- DiskCacheStrategy.SOURCE:表示只缓存原始图片。
- DiskCacheStrategy.RESULT:表示只缓存转换过后的图片(默认选项)。
- DiskCacheStrategy.ALL :表示既缓存原始图片,也缓存转换过后的图片。
2.4 Glide加载图片主要类
首先我们想下一个图片框架,应该包含哪几个模块:
对外接口:封装该框架的功能接口,一般为单例模式。
获取图片Request:为每个图片加载创建一个Request,用来准备数据、请求图片资源。
异步处理:不管从网络还是从本地读取图片都是耗时操作,需要在子线程中完成。
网络连接 :网络获取图片的必备模块。
解码 :获得图片流后要解码得到图片对象。
上面几部分是图片框架所必备的模块,Glide也不例外,接下来看看Glide各模块对应的主要类:
Glide:Glide除了是接口封装类,还负责创建全局使用的工具和组件。
GenericRequest:为每个图片加载创建一个Request,初始化这张图片的转码器、图片变换器、图片展示器target等,当然这个过程实在GenericRequestBuilder的实现类里完成的。
Engine:异步处理总调度器。EnginJob负责线程管理,EngineRunnable是一个异步处理线程。DecodeJob是真正线程里获取和处理图片的地方。
HttpUrlFetcher :获取网络流,使用的是HttpURLConnection。
Decoder :读取网络流后,解码得到Bitmap或者gifResource。因为加载图片类型不同,这快分支较多,学习Glide初级阶段,这块可以先不再详细分析。
接下来我们进入主题,缓存的代码在上面流程图里的什么位置?内存缓存的操作应该是在异步处理之前,磁盘缓存是耗时操作应该是在异步处理中完成。
2.5 Glide内存缓存源码分析
内存缓存的 读存都在Engine类中完成。
2.5.1 Glide内存缓存的特点
内存缓存使用弱引用和LruCache结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类
Resources内部有个计数器判断是该图片否正在使用。
2.5.2 Glide内存缓存的流程
- 读:是先从lruCache取,取不到再从弱引用中取;
- 存:内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;
- 渲染完图片,图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。
具体看源码:
Engine在加载流程的中的入口方法是load方法:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId();
//生成缓存的key
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
//从LruCache获取缓存图片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//从弱引用获取图片
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
...
}
上面是从内存缓存中读取图片的主流程:
- 生成缓存的key。
- 从LruCache获取缓存图片。
- LruCache没取到,从弱引用获取图片。
- 内存缓存取不到,进入异步处理。
我们具体看取图片的两个方法loadFromCache()和loadFromActiveResources()。loadFromCache使用的就是LruCache算法,loadFromActiveResources使用的就是弱引用。我们来看一下它们的源码:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true );
}
return result;
}
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
if (activeRef != null) {
active = activeRef.get();
if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
}
return active;
}
...
}
loadFromCache()方法:
- 首先就判断isMemoryCacheable是不是false,如果是false的话就直接返回null。这就是skipMemoryCache()方法设置的是否内存缓存已被禁用。
- 然后调用getEngineResourceFromCache()方法来获取缓存。在这个方法中,会从中获取图片缓存LruResourceCache,LruResourceCache其实使用的就是LruCache算法实现的缓存。
- 当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,将缓存图片存储到activeResources当中。activeResources就是弱引用的HashMap,用来缓存正在使用中的图片。
loadFromActiveResources()方法:
- 就是从activeResources这个activeResources当中取值的。使用activeResources来缓存正在使用中的图片,用来保护正在使用中的图片不会被LruCache算法回收掉。
这样我们把从内存读取图片缓存的流程搞清了,那是什么时候存储的呢。想想什么时候合适?是不是应该在异步处理获取到图片后,再缓存到内存?
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
//将正在加载的图片放到弱引用缓存
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}
...
}
在onEngineJobComplete()方法里将正在加载的图片放到弱引用缓存。那什么时候放在LruCache里呢?当然是在使用完,那什么时候使用完呢?
那我们来看EngineResource这个类是怎么标记自己是否在被使用的。EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,代码如下所示:
class EngineResource<Z> implements Resource<Z> {
private int acquired;
...
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
}
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
}
可以看出当引用计数acquired变量为0,就是没有在使用了,然后调用了listener.onResourceReleased(key, this);
这个listener就是Engine对象,我们来看下它的onResourceReleased()方法:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
做了三件事:
- 从弱引用删除图片缓存
- 是否支持缓存,缓存到LruCache缓存
- 不支持缓存直接调用垃圾回收,回收图片
到这里内存缓存的读和存的流程就介绍完了,根据源码回头看看我们之前列的Glide内存缓存流程,就清晰很多了。
2.6 Glide磁盘缓存源码分析
2.6.1 Glide磁盘缓存流程
先列下主流程,再具体看代码
- 读:先找处理后(result)的图片,没有的话再找原图。
- 存:先存原图,再存处理后的图。
注意一点:diskCacheStrategy设置的的缓存模式即影响读取,也影响存储。
具体看源码:
源码入口位置是在EngineRunnable的run()方法,run()方法中调用到decode()方法,decode()方法的源码:
private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
//从磁盘缓存读取图片
return decodeFromCache();
} else {
//从原始位置读取图片
return decodeFromSource();
}
}
来看一下decodeFromCache()方法的源码,如下所示:
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
//先尝试读取处理后的缓存图
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
}
if (result == null) {
//再尝试读取原图的缓存图
result = decodeJob.decodeSourceFromCache();
}
return result;
}
处理后的缓存图和原图缓存图对应的是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE这两个缓存模式。
到DecodeJob具体看下这两个读取磁盘缓存的方法,decodeResultFromCache()和decodeSourceFromCache():
public Resource<Z> decodeResultFromCache() throws Exception {
if (!diskCacheStrategy.cacheResult()) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<T> transformed = loadFromCache(resultKey);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
return result;
}
public Resource<Z> decodeSourceFromCache() throws Exception {
if (!diskCacheStrategy.cacheSource()) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
return transformEncodeAndTranscode(decoded);
}
这里两个方法都先判断了是否是对应的缓存模式,不是则读取失败。这里我们不关注transform 和transcode的相关功能,只分析缓存功能。两个缓存方法都调用到了loadFromCache()方法,只是传入的key不同。一个是处理后图片的key,一个是原始图片的key。
继续看loadFromCache()方法的源码:
private Resource<T> loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource<T> result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}
源码中可以看到我们是从diskCacheProvider.getDiskCache()中读取的缓存,diskCacheProvider.getDiskCache()获得的是DiskLruCache工具类的实例,然后从DiskLruCache获取缓存。之后的decode不是本篇的关注点,先不分析。
到这里我们把从磁盘缓存读取缓存的流程讲完了,那什么时候存入的呢?肯定是在从原始位置获取图片后,我们回到decodeFromSource()方法,一步步看进去:
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
decodeSource()顾名思义是用来解析原图片的,而transformEncodeAndTranscode()则是用来对图片进行转换和转码的。我们先来看decodeSource()方法:
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
//从网络获取图片
final A data = fetcher.loadData(priority);
if (isCancelled) {
return null;
}
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
//判断设置了是否缓存原图
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
}
return decoded;
}
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
long startTime = LogTime.getLogTime();
SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
startTime = LogTime.getLogTime();
Resource<T> result = loadFromCache(resultKey.getOriginalKey());
return result;
}
decodeSource()方法中获取图片后,调用到decodeFromSourceData()方法,然后判断是否缓存原图,是的话就调用到cacheAndDecodeSourceData(A data)方法。看进去,还是调用了 diskCacheProvider.getDiskCache()获取DiskLruCache工具类的实例。然后调用put方法缓存了原图。
到此我们缓存了原图,处理后的图片是什么时候缓存的?肯定是在图片处理之后,在transformEncodeAndTranscode()方法中:
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
return result;
}
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTime.getLogTime();
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
diskCacheProvider.getDiskCache().put(resultKey, writer);
}
transformEncodeAndTranscode中先对图片进行了转换,然后调用writeTransformedToCache(transformed);判断是否缓存处理后的图片,是就对处理后的图片进行了缓存。调用的同样是DiskLruCache实例的put()方法,不过这里用的缓存Key是resultKey。
2.7 缓存机制时序图
写在最后
本篇文章是参考了几篇简书上的文章,以及自己的部分总结而成对Glide的分析,有需要的同学可以进行相关的查阅
参考文章:https://www.jianshu.com/p/17644406396b
https://www.jianshu.com/p/7ce7b02988a4