Android⾯试题:Glide
⽬录
系列⽂章:
相关⽂章:
(以Glide3.7.0源码为例 共8篇 第2篇较难懂 如果不太理解可以跳过直接从第3篇 缓存开始读 )
(以Glide3.7.0源码为例 ⼀共12篇 前⾯5篇没什么内容,可以从第6篇开始看)
Q1:看过Glide源码吗,你印象最深的是什么?
Glide缓存简介
Glide的缓存设计可以说是⾮常先进的,考虑的场景也很周全。在缓存这⼀功能上,Glide⼜将它分成了两个模块,⼀个是内存缓存,⼀个是硬盘缓存。
这两个缓存模块的作⽤各不相同,内存缓存的主要作⽤是防⽌应⽤重复将图⽚数据读取到内存当中,⽽硬盘缓存的主要作⽤是防⽌应⽤重复从⽹络或其他地⽅重复下载和读取数据。
内存缓存和硬盘缓存的相互结合才构成了Glide极佳的图⽚缓存效果,那么接下来我们就分别来分析⼀下这两种缓存的使⽤⽅法以及它们的实现原理。
Q2:简单说⼀下Glide的三级缓存?
相关⽂章:
简单描述:
读取的顺序是:Lru算法缓存、弱引⽤缓存、磁盘缓存
写⼊的顺序是:弱引⽤缓存、Lru算法缓存、磁盘缓存(不准确)
下⾯叙述⼀下三级缓存的流程:()
当我们的APP中想要加载某张图⽚时,先去LruCache中寻图⽚,如果LruCache中有,则直接取出来使⽤,如果LruCache中没有,则去WeakReference中寻,如果WeakReference中有,则从WeakReference中取出图⽚使⽤,同时将图⽚重新放回到LruCache中,如果WeakReference中也没有图⽚,则去⽂件系统中寻,如果有则取出来使⽤,同时将图⽚添加到LruCache中,如果没有,则连接⽹络从⽹上下载图⽚。图⽚下载完成后,将图⽚保存到⽂件系统中,然后放到LruCache中。
严格来讲,并没有什么Glide的三级缓存,因为Glide的缓存只有两个模块,⼀个是内存缓存,⼀个是磁盘缓存。其中内存缓存⼜分为Lru算法的缓存和弱引⽤缓存。
LruCache算法,Least Recently Used,⼜称为近期最少使⽤算法。主要算法原理就是把最近所使⽤的对象的强引⽤存储在LinkedHashMap上,并且,把最近最少使⽤的对象在缓存池达到预设值之前从内存中移除。
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
}
弱引⽤缓存:
//弱引⽤缓存
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>();
}
Glide缓存机制⼤致分为三层:Lru算法缓存、弱引⽤缓存、磁盘缓存。
读取的顺序是:Lru算法缓存、弱引⽤缓存、磁盘缓存
写⼊的顺序是:弱引⽤缓存、Lru算法缓存、磁盘缓存(不准确)
我们先来看读取:Lru算法缓存、弱引⽤缓存、磁盘缓存
⾸先 memoryCache的初始值是⼀个LruResourceCache对象,即默认是lru算法的缓存。
GlideBuilder.java
private MemoryCache memoryCache;
Glide createGlide() {
memoryCache = new MemoryCacheSize());
}
Engine.java
private final MemoryCache cache;
public <T, Z, R> LoadStatus load(...){
...
//获取Lru算法的缓存,如果没有,就从弱引⽤中获取缓存
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
...
//从弱引⽤中获取缓存
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
...
/
/从磁盘中获取缓存
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);
}
接下来我们看写⼊:弱引⽤缓存、Lru算法缓存、磁盘缓存
内存缓存的写⼊:
EngineResource是⽤⼀个acquired(int)变量⽤来记录图⽚被引⽤的次数,调⽤acquire()⽅法会让变量加1,调⽤release()⽅法会让变量减1。
acquired变量⼤于0的时候,说明图⽚正在使⽤中,也就应该放到activeResources弱引⽤缓存当中;
如果acquired变量等于0了,说明图⽚已经不再被使⽤了,那么此时会调⽤⽅法来释放资源,
这⾥⾸先会将缓存图⽚从activeResources中移除,然后再将它put到LruResourceCache当中。
这样也就实现了正在使⽤中的图⽚使⽤弱引⽤来进⾏缓存,不在使⽤中的图⽚使⽤LruCache来进⾏缓存的功能
...
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
cacheable// 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()));
}
}
}
...
}
现在就⾮常明显了,可以看到,在第13⾏,回调过来的EngineResource被put到了activeResources当中,也就是在这⾥写⼊的缓存。
那么这只是弱引⽤缓存,还有另外⼀种LruCache缓存是在哪⾥写⼊的呢?这就要介绍⼀下EngineResource中的⼀个引⽤机制了。观察刚才的handleResultOnMainThread()⽅法,在第15⾏和第19⾏有调⽤EngineResource的acquire()⽅法,在第23⾏有调⽤它的release()⽅法。其实,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 (!MainLooper().Looper())) {
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 (!MainLooper().Looper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
}
}
}
也就是说,当acquired变量⼤于0的时候,说明图⽚正在使⽤中,也就应该放到activeResources弱引⽤缓存当中。⽽经过release()之后,如果acquired变量等于0了,说明图⽚已经不再被使⽤了,那么此时会在第24⾏调⽤listener的onResourceReleased()⽅法来释放资源,这个listener就是Engine对象,我们来看下它的onResourceReleased()⽅法:
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
}
}
...
}
可以看到,这⾥⾸先会将缓存图⽚从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使⽤中的图⽚使⽤弱引⽤来进⾏缓存,不在使⽤中的图⽚使⽤LruCache来进⾏缓存的功能。
这就是Glide内存缓存的实现原理。
Q3:Glide加载⼀个⼀兆的图⽚(100*100),是否会压缩后再加载,放到⼀个200*200的view上会怎样,1000*1000呢,图⽚会很模糊,怎么处理?
⽽使⽤Glide,我们就完全不⽤担⼼图⽚内存浪费,甚⾄是内存溢出的问题。因为Glide从来都不会直接将图⽚的完整尺⼨全部加载到内存中,⽽是⽤多少加载多少。Glide会⾃动判断ImageView的⼤⼩,然后只将这么⼤的图⽚像素加载到内存当中,帮助我们节省内存开⽀。
ImageView默认的scaleType是FIT_CENTER
FitCenter的效果:会将图⽚按照原始的长宽⽐充满全屏
那么,Glide将图⽚压缩到什么程度呢?.
在默认情况下Picasso和Glide的外部缓存机制是⾮常不⼀样的,通过实验可以发现(1920x1080 像素的图⽚被加载到768x432像素的imageview中),Glide缓存的是768x432像素的图⽚,⽽Picasso缓存的是整张图⽚(1920x1080像素)。
当我们调整imageview的⼤⼩时,Picasso会不管imageview⼤⼩是什么,总是直接缓存整张图⽚,⽽Gli
de就不⼀样了,它会为每个不同尺⼨的Imageview缓存⼀张图⽚,也就是说不管你的这张图⽚有没有加载过,只要imageview的尺⼨不⼀样,那么Glide就会重新加载⼀次,这时候,它会在加载的imageview之前从⽹络上重新下载,然后再缓存。
防⽌各位不明⽩,再来举个例⼦,如果⼀个页⾯的imageview是200*200像素,⽽另⼀个页⾯中的imageview是100*100像素,这时候想要让两个imageview像是同⼀张图⽚,那么Glide需要下载两次图⽚,并且缓存两张图⽚。
Q4:Glide 缓存原理,如何设计⼀个⼤图加载框架。
概括来说,图⽚加载包含封装,解析,下载,解码,变换,缓存,显⽰等操作。
流程图如下:
Q5:LRUCache 原理。
LruCache算法,⼜称为近期最少使⽤算法。主要算法原理就是把最近所使⽤的对象的强引⽤存储在LinkedHashMap上,并且,把最近最少使⽤的对象在缓存池达到预设值之前从内存中移除。
Q6:Glide VS Picasso
双胞胎兄弟之间的对⽐,使⽤⽅式相同,但 Glide 之所以胜出,不仅仅是 Google的推荐,更多应该归功于 GIF 的⽀持。 在没有 Glide 之前,常⽤的做法就是写了个⾃定义 view 然后 ⽤⼀个 media 去播放。有了 Glide 之后⼏乎对于 GIF ⽆感知了的, 内部已经⽀持了的。可以像普通图⽚那样去加载并且显⽰出来动图。
图⽚显⽰的质量配置主要分为四种:
ARGB_8888 :32位图,带透明度,每个像素占4个字节
ARGB_4444 :16位图,带透明度,每个像素占2个字节
RGB_565 :16位图,不带透明度,每个像素占2个字节
ALPHA_8 :32位图,只有透明度,不带颜⾊,每个像素占4个字节
(A代表透明度,RGB代表红绿蓝:即颜⾊)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论