Android图片缓存及缓存算法(Universal-Image-Loader)

内存缓存

缓存与内存回收机制有关,java中有四种与垃圾回收(gc)有关的引用:强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)。

强引用(StrongReference)

强引用是最普遍的一种引用,在java中使用new关键字生成的对象就是强引用,对于强引用,java垃圾回收机制不会将其回收,当内存不足时,java虚拟机宁肯抛出OutOfMemoryError错误也不会随意回收强引用的对象。

软引用(SoftReference)

对于软引用,当内存足够时,不会回收软引用的对象,但是当内存空间不足时,就会回收这些对象。

弱引用(WeakReference)

弱引用,相对于软引用来说,更容易被回收机制回收,当垃圾回收机制扫描内存空间时,如果发现弱引用对象,不论当前的内存是否充足,都会立即将其回收,释放内存空间。

虚引用(PhantomReference)

虚引用不同于其他引用,其并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

Universal-Image-Loader中的缓存机制

1、LRU(Least Recently Used)最近最久未使用页面置换算法

LRU是操作系统中页面置换算法的一种,其核心是,对于每一个页面,设置一个时间字段,用来记录这个页面自上次被访问以来所经历的时间t,当出现缺页中断(也就是内存不足)时,将t值最大的页面删除。

2、Universal-Image-Loader中缓存机制

(1)只使用的是强引用缓存

LruMemoryCache(使用LRU算法,缓存对象的是bitmap的强引用)

(2)使用强引用和弱引用相结合的缓存有

UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)

LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)

FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)

LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)

LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)

(3)只使用弱引用缓存

WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)

LruMemoryCache源码分析

package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.cache.memory.MemoryCache;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
 * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
 * become eligible for garbage collection.
*
* NOTE: This cache uses only strong references for stored Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.8.1 */ public class LruMemoryCache implements MemoryCache { private final LinkedHashMap map; private final int maxSize; /** Size of this cache in bytes */ private int size; /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */ public LruMemoryCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap(0, 0.75f, true); } /** * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head * of the queue. This returns null if a Bitmap is not cached. */ @Override public final Bitmap get(String key) { if (key == null) { throw new NullPointerException("key == null"); } synchronized (this) { return map.get(key); } } /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */ @Override public final boolean put(String key, Bitmap value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } synchronized (this) { size += sizeOf(key, value); Bitmap previous = map.put(key, value); if (previous != null) { size -= sizeOf(key, previous); } } trimToSize(maxSize); return true; } /** * Remove the eldest entries until the total of remaining entries is at or below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements. */ private void trimToSize(int maxSize) { while (true) { String key; Bitmap value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry toEvict = map.entrySet().iterator().next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= sizeOf(key, value); } } } /** Removes the entry for {@code key} if it exists. */ @Override public final Bitmap remove(String key) { if (key == null) { throw new NullPointerException("key == null"); } synchronized (this) { Bitmap previous = map.remove(key); if (previous != null) { size -= sizeOf(key, previous); } return previous; } } @Override public Collection keys() { synchronized (this) { return new HashSet(map.keySet()); } } @Override public void clear() { trimToSize(-1); // -1 will evict 0-sized elements } /** * Returns the size {@code Bitmap} in bytes. *

* An entry's size must not change while it is in the cache. */ private int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public synchronized final String toString() { return String.format("LruCache[maxSize=%d]", maxSize); } }

在源码中LinkedHashMap作为LRU算法的缓存,LinkedHashMap是HashMap的一个子类,它保留插入的顺序,LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序

默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。

this.map = new LinkedHashMap(0, 0.75f, true);

构造方法中第一个参数代表LinkedHashMap初始容量,第二个参数0.75f是加载因子,第三个参数是访问顺序,默认为false即插入顺序,true为访问顺序。
LruMemoryCache中维护了一个所有bitmap的size和,如果size超过设定的maxSize,就会将最近最久未使用的图片缓存删除。

在源码的put方法中调用了HashMap中的put方法:

public V put(K key, V value) {  
        if (key == null)  
            return putForNullKey(value);  
        int hash = hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        for (Entry e = table[i]; e != null; e = e.next) {  
            Object k;  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
                V oldValue = e.value;  
                e.value = value;  
                e.recordAccess(this);  
                return oldValue;  
            }  
        }  
  
        modCount++;  
        addEntry(hash, key, value, i);  
        return null;  
    } 
        void recordAccess(HashMap m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            //当LinkedHashMap按访问排序时
            if (lm.accessOrder) {
                lm.modCount++;
                //移除当前节点
                remove();
                //将当前节点插入到头结点前面
                addBefore(lm.header);
            }
        }
/**
        * 移除节点,并修改前后引用
        */
       private void remove() {
           before.after = after;
           after.before = before;
       }

       /**
        * 将当前节点插入到existingEntry的前面
        */
       private void addBefore(Entry existingEntry) {
           after  = existingEntry;
           before = existingEntry.before;
           before.after = this;
           after.before = this;
       }

HashMap中put方法源码,当HashMap中存在这个value时,会将这个value返回,否则返回null,所以当map.put(key, value)返回值不为空时,需要减去刚加入的图片size。当返回为null时,将bitmap加入到LinkedHashMap中,返回为bitmap时,直接使用该bitmap。recordAccess(HashMap m)在HashMap的put和get方法中,会调用该方法,在HashMap中该方法为空,在LinkedHashMap中,当按访问顺序排序时,该方法会将当前节点插入到链表尾部(头结点的前一个节点),否则不做任何事(最近使用的bitmap会移动到双向链表的尾部)。

LRU算法实现的核心是private void trimToSize(int maxSize)函数中不断判断当前size是否大于maxSize,当超过maxSize时,会将LinkedHashMap中的第一个元素删除,其就是最近最久未使用的bitmap。

使用LinkedHashMap可以很容易构造一个基于LRU算法的缓存。

UsingFreqLimitedMemoryCache源码分析

/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.memory;

import android.graphics.Bitmap;

import java.lang.ref.Reference;
import java.util.*;

/**
 * Base memory cache. Implements common functionality for memory cache. Provides object references (
 * {@linkplain Reference not strong}) storing.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.0.0
 */
public abstract class BaseMemoryCache implements MemoryCache {

    /** Stores not strong references to objects */
    private final Map> softMap = Collections.synchronizedMap(new HashMap>());

    @Override
    public Bitmap get(String key) {
        Bitmap result = null;
        Reference reference = softMap.get(key);
        if (reference != null) {
            result = reference.get();
        }
        return result;
    }

    @Override
    public boolean put(String key, Bitmap value) {
        softMap.put(key, createReference(value));
        return true;
    }

    @Override
    public Bitmap remove(String key) {
        Reference bmpRef = softMap.remove(key);
        return bmpRef == null ? null : bmpRef.get();
    }

    @Override
    public Collection keys() {
        synchronized (softMap) {
            return new HashSet(softMap.keySet());
        }
    }

    @Override
    public void clear() {
        softMap.clear();
    }

    /** Creates {@linkplain Reference not strong} reference of value */
    protected abstract Reference createReference(Bitmap value);
}

/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.memory;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.utils.L;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Limited cache. Provides object storing. Size of all stored bitmaps will not to exceed size limit (
 * {@link #getSizeLimit()}).
*
* NOTE: This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @see BaseMemoryCache * @since 1.0.0 */ public abstract class LimitedMemoryCache extends BaseMemoryCache { private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16; private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024; private final int sizeLimit; private final AtomicInteger cacheSize; /** * Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed * limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any * time) */ private final List hardCache = Collections.synchronizedList(new LinkedList()); /** @param sizeLimit Maximum size for cache (in bytes) */ public LimitedMemoryCache(int sizeLimit) { this.sizeLimit = sizeLimit; cacheSize = new AtomicInteger(); if (sizeLimit > MAX_NORMAL_CACHE_SIZE) { L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB); } } @Override public boolean put(String key, Bitmap value) { boolean putSuccessfully = false; // Try to add value to hard cache int valueSize = getSize(value); int sizeLimit = getSizeLimit(); int curCacheSize = cacheSize.get(); if (valueSize < sizeLimit) { while (curCacheSize + valueSize > sizeLimit) { Bitmap removedValue = removeNext(); if (hardCache.remove(removedValue)) { curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); } } hardCache.add(value); cacheSize.addAndGet(valueSize); putSuccessfully = true; } // Add value to soft cache super.put(key, value); return putSuccessfully; } @Override public Bitmap remove(String key) { Bitmap value = super.get(key); if (value != null) { if (hardCache.remove(value)) { cacheSize.addAndGet(-getSize(value)); } } return super.remove(key); } @Override public void clear() { hardCache.clear(); cacheSize.set(0); super.clear(); } protected int getSizeLimit() { return sizeLimit; } protected abstract int getSize(Bitmap value); protected abstract Bitmap removeNext(); }
/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to
 * exceed size limit. When cache reaches limit size then the bitmap which used the least frequently is deleted from
 * cache.
*
* NOTE: This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.0.0 */ public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache { /** * Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache * size will exceed limit then object with the least frequently usage is deleted (but it continue exist at * {@link #softMap} and can be collected by GC at any time) */ private final Map usingCounts = Collections.synchronizedMap(new HashMap()); public UsingFreqLimitedMemoryCache(int sizeLimit) { super(sizeLimit); } @Override public boolean put(String key, Bitmap value) { if (super.put(key, value)) { usingCounts.put(value, 0); return true; } else { return false; } } @Override public Bitmap get(String key) { Bitmap value = super.get(key); // Increment usage count for value if value is contained in hardCahe if (value != null) { Integer usageCount = usingCounts.get(value); if (usageCount != null) { usingCounts.put(value, usageCount + 1); } } return value; } @Override public Bitmap remove(String key) { Bitmap value = super.get(key); if (value != null) { usingCounts.remove(value); } return super.remove(key); } @Override public void clear() { usingCounts.clear(); super.clear(); } @Override protected int getSize(Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override protected Bitmap removeNext() { Integer minUsageCount = null; Bitmap leastUsedValue = null; Set> entries = usingCounts.entrySet(); synchronized (usingCounts) { for (Entry entry : entries) { if (leastUsedValue == null) { leastUsedValue = entry.getKey(); minUsageCount = entry.getValue(); } else { Integer lastValueUsage = entry.getValue(); if (lastValueUsage < minUsageCount) { minUsageCount = lastValueUsage; leastUsedValue = entry.getKey(); } } } } usingCounts.remove(leastUsedValue); return leastUsedValue; } @Override protected Reference createReference(Bitmap value) { return new WeakReference(value); } }

UsingFreqLimitedMemoryCache继承自LimitedMemoryCach,而LimitedMemoryCach继承自BaseMemoryCache。BaseMemoryCache中维护了Map> softMap包含软引用类型bitmap的softMap,定义了softMap的get、put、remove和clear方法。

LimitedMemoryCache继承BaseMemoryCache,其中定义了MAX_NORMAL_CACHE_SIZE_IN_MBMAX_NORMAL_CACHE_SIZE、内存大小限制sizeLimit和List hardCache(用来维护该算法的bitmap列表)。当进行put操作时,首先判断put的bitmap是否比限制的sizeLimit还大,如果小于sizeLimit,循环判断当前缓存的cacheSize(AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。AtomicInteger提供原子操作来进行Integer的使用,因此十分适合高并发情况下的使用。)加上要put的bitmap的size是否大于sizeLimit,如果大于sizeLimit,则调用算法删除频率最小的bitmap。

removeNext(),在UsingFreqLimitedMemoryCache中实现,其使用Map usingCounts保存内存中bitmap的使用次数。当调用removeNext()方法时,遍历Map usingCounts,找到使用次数最少的bitmap并返回。

FIFOLimitedMemoryCache源码分析

/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to
 * exceed size limit. When cache reaches limit size then cache clearing is processed by FIFO principle.
*
* NOTE: This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.0.0 */ public class FIFOLimitedMemoryCache extends LimitedMemoryCache { private final List queue = Collections.synchronizedList(new LinkedList()); public FIFOLimitedMemoryCache(int sizeLimit) { super(sizeLimit); } @Override public boolean put(String key, Bitmap value) { if (super.put(key, value)) { queue.add(value); return true; } else { return false; } } @Override public Bitmap remove(String key) { Bitmap value = super.get(key); if (value != null) { queue.remove(value); } return super.remove(key); } @Override public void clear() { queue.clear(); super.clear(); } @Override protected int getSize(Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override protected Bitmap removeNext() { return queue.remove(0); } @Override protected Reference createReference(Bitmap value) { return new WeakReference(value); } }

FIFOLimitedMemoryCache也继承了LimitedMemoryCache,并维护了一个LinkedList,与ArrayList相比,LinkedList中包含了一个双向循环链表,提供了类似栈和队列的操作。

LinkedList的add方法:

 public boolean add(E e) {
     addBefore(e, header);
     return true;
 }
 private Entry addBefore(E e, Entry entry) {
     Entry newEntry = new Entry(e, entry, entry.previous);
     newEntry.previous.next = newEntry;
     newEntry.next.previous = newEntry;
     size++;
     modCount++;
     return newEntry;
 }

向LinkedList中插入元素时,是将元素插入到了header的后面addBefore(e, header);,即将元素插入到链表的尾部。

public E remove(int index) {
    return remove(entry(index));
}

private Entry entry(int index) {
         if (index < 0 || index >= size)
             throw new IndexOutOfBoundsException("Index: "+index+
                                                 ", Size: "+size);
         Entry e = header;
         // 根据这个判断决定从哪个方向遍历这个链表
         if (index < (size >> 1)) {
             for (int i = 0; i <= index; i++)
                 e = e.next;
        } else {
            // 可以通过header节点向前遍历,说明这个一个循环双向链表,header的previous指向链表的最后一个节点,这也验证了构造方法中对于header节点的前后节点均指向自己的解释
            for (int i = size; i > index; i--)
                e = e.previous;
        }
       return e;
    }

FIFOLimitedMemoryCache的removeNext()方法中queue.remove(0);是将双向循环列表的第一个元素删除。由此可知由于插入元素时是向双向循环列表中尾部插入新元素,删除时,删除列表的第一个元素,即先进先出算法。
除此之外,LargestLimitedMemoryCache和LimitedAgeMemoryCach都继承了LimitedMemoryCache,是强引用和弱引用相结合的缓存,因为BaseMemoryCache中维护了一个软引用的HashMap>(),LimitedMemoryCache中维护了一个强引用的LinkedList(),只是具体实现算法不同而已。

WeakMemoryCache源码分析

/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.BaseMemoryCache;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;

/**
 * Memory cache with {@linkplain WeakReference weak references} to {@linkplain android.graphics.Bitmap bitmaps}
*
* NOTE: This cache uses only weak references for stored Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.5.3 */ public class WeakMemoryCache extends BaseMemoryCache { @Override protected Reference createReference(Bitmap value) { return new WeakReference(value); } }

WeakMemoryCache继承BaseMemoryCache,返回一个 WeakReference,其他方法使用父类方法。

参考文章:
http://blog.csdn.net/xiaanming/article/details/27525741
http://www.cnblogs.com/children/archive/2012/10/02/2710624.html
http://blog.csdn.net/jzhf2012/article/details/8540543

你可能感兴趣的:(Android图片缓存及缓存算法(Universal-Image-Loader))