Glide目前几乎是Android图片框架的不二选择,那么你知道Glide有哪些优点吗?
今天的主题是一起来研究一下Glide的核心原理。
Glide的三级缓存原理是什么?
读取一张图片的顺序:LRU算法缓存->弱引用缓存->磁盘缓存(如果设置了的话)。
我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,并将该图片放入WeakReference中,如果LruCache中没有,则去WeakReference中寻找,如果WeakReference中有,则从WeakReference中取出图片使用,如果WeakReference中也没有图片,则从磁盘缓存/网络中加载图片。
注:图片正在使用时存在于 activeResources 弱引用map中。
缓存图片的写入顺序:弱引用缓存->LRU算法缓存->磁盘缓存。
当图片不存在的时候,先从网络下载图片,然后将图片存入弱引用中,glide会采用一个acquired(int)变量用来记录图片被引用的次数,当acquired变量大于0的时候,说明图片正在使用中,也就是将图片放到弱引用缓存当中;如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用方法来释放资源,首先会将缓存图片从弱引用中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不再使用
中的图片使用LruCache来进行缓存的功能。
关于LRUCache:
最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。LruCache 内部用LinkHashMap存取数据,在双向链表保证数据新旧顺序的前提下,设置一个最大内存,往里面put数据的时候,当数据达到最大内存的时候,将最老的数据移除掉,保证内存不超过设定的最大值。
关于LinkedHashMap:
LinkHashMap 继承HashMap,在 HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)。
前面说了一堆,其实重点大家也很明白,就是这个LRUCache:
- public class LruCache
{ - private final LinkedHashMap
cache = - new LinkedHashMap
(100, 0.75f, true);//最后一个传入true,代表基于顺序访问 - private int maxSize;
- private final int initialMaxSize;
- private int currentSize = 0;
-
- public LruCache(int size) {
- this.initialMaxSize = size;
- this.maxSize = size;
- }
-
- public void setSizeMultiplier(float multiplier) {
- if (multiplier < 0) {
- throw new IllegalArgumentException("Multiplier must be >= 0");
- }
- maxSize = Math.round(initialMaxSize * multiplier);
- evict();
- }
-
- protected int getSize(Y item) {
- return 1;
- }
-
-
- protected void onItemEvicted(T key, Y item) {
- // optional override
- }
-
- public int getMaxSize() {
- return maxSize;
- }
-
- public int getCurrentSize() {
- return currentSize;
- }
-
-
- public boolean contains(T key) {
- return cache.containsKey(key);
- }
-
-
- public Y get(T key) {
- return cache.get(key);
- }
-
-
- public Y put(T key, Y item) {
- final int itemSize = getSize(item);
- if (itemSize >= maxSize) {
- onItemEvicted(key, item);
- return null;
- }
-
- final Y result = cache.put(key, item);
- if (item != null) {
- currentSize += getSize(item);
- }
- if (result != null) {
- // TODO: should we call onItemEvicted here?
- currentSize -= getSize(result);
- }
- evict();
-
- return result;
- }
-
- public Y remove(T key) {
- final Y value = cache.remove(key);
- if (value != null) {
- currentSize -= getSize(value);
- }
- return value;
- }
-
-
- public void clearMemory() {
- trimToSize(0);
- }
-
-
- protected void trimToSize(int size) {
- Map.Entry
last; - while (currentSize > size) {
- last = cache.entrySet().iterator().next();//基于顺序访问
- final Y toRemove = last.getValue();
- currentSize -= getSize(toRemove);
- final T key = last.getKey();
- cache.remove(key);
- onItemEvicted(key, toRemove);
- }
- }
-
- private void evict() {
- trimToSize(maxSize);
- }
- }
解释一下一个重要的成员变量maxSize是什么意思:资源的最大字节数。
我们先来分析一下put方法:
再来分析一下evict(意思是逐出)方法:
我们再来看下LRUCache的子类来加深我们的理解:
- public class LruResourceCache extends LruCache
> - implements MemoryCache {
- private ResourceRemovedListener listener;
-
- /**
- * Constructor for LruResourceCache.
- *
- * @param size The maximum size in bytes the in memory cache can use.
- */
- public LruResourceCache(int size) {
- super(size);
- }
-
- @Override
- public void setResourceRemovedListener(ResourceRemovedListener listener) {
- this.listener = listener;
- }
-
- @Override
- protected void onItemEvicted(Key key, Resource> item) {
- if (listener != null) {
- listener.onResourceRemoved(item);
- }
- }
-
- @Override
- protected int getSize(Resource> item) {
- return item.getSize();
- }
-
- @SuppressLint("InlinedApi")
- @Override
- public void trimMemory(int level) {
- if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
- // Nearing middle of list of cached background apps
- // Evict our entire bitmap cache
- clearMemory();
- } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
- // Entering list of cached background apps
- // Evict oldest half of our bitmap cache
- trimToSize(getCurrentSize() / 2);
- }
- }
- }
时间有点晚了,后面再用一篇文章来分析一下LinkedHashMap的数据结构,晚安,好梦!