在Android中,有一个叫做LruCache类专门用来做图片缓存处理的。
它有一个特点,当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。
我们来看看它的源码(注意是在android.support.v4.util下):
package android.support.v4.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
//内部维护了一个LinkedHashMap,为什么要用这个,后面介绍
private final LinkedHashMap<K, V> map;
private int size; //已缓存的大小
private int maxSize; //缓存最大值
private int putCount; //往集合里put的次数
private int createCount; //创建的个数
private int evictionCount;//移除的个数
private int hitCount;//命中次数
private int missCount;//丢失的次数
/** * 构造方法 * @param maxSize 缓存的最大值 */
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/** * 重新设置最大缓存大小 * * @param maxSize */
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
/** * 通过key获取相应的value,或者创建返回相应的value。相应的value会移动到队列的头部, * 如果item的value没有被cache或者不能被创建,则返回null。 */
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//如果丢失了就试图创建一个value
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//同步,保证多线程下访问安全
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// 如果前面存在一个旧的value,那么撤销put()
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
/** * 在缓存集合(LinkedHashMap)队首添加value * * @return the previous value mapped by {@code key}. */
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
//返回的先前的value
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/** * 调整cache空间 * 当缓存的大小超过原来设定的maxSize时,遍历map,将多余的项(代码中对应toEvict)剔除掉,直到当 * 前cache的大小等于或小于限定的大小 * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */
public void trimToSize(int maxSize) {
while (true) {
K key;
V 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<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
/** * 从缓存中移除掉key对应的value * * @return the previous value mapped by {@code key}. */
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/** * 当item被回收或者删掉时调用。该方法当value被回收释放存储空间时被remove调用, * 或者替换item值时put调用,默认实现什么都没做。 * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
/** * 当某Item丢失时会调用到,返回计算的相应的value或者null * <p>If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */
protected V create(K key) {
return null;
}
/** * 计算某个item的大小,如果是Bitmap,这应该是bitmap.getByteCount(); */
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
protected int sizeOf(K key, V value) {
return 1;
}
/** * 清空缓存 */
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. */
public synchronized final int size() {
return size;
}
/** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. */
public synchronized final int maxSize() {
return maxSize;
}
/** * Returns the number of times {@link #get} returned a value that was * already present in the cache. */
public synchronized final int hitCount() {
return hitCount;
}
/** * 返回get()为null的次数或者创建的次数 */
public synchronized final int missCount() {
return missCount;
}
/** * 返回创建create()的次数 */
public synchronized final int createCount() {
return createCount;
}
/** * 返回put的次数 */
public synchronized final int putCount() {
return putCount;
}
/** * 返回被回收的数量 */
public synchronized final int evictionCount() {
return evictionCount;
}
/** * 返回当前cache的副本,从最近最少访问到最多访问 */
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
我们来分析下LinkedHashMap:
LinkedHashMap中的get()方法不仅返回所匹配的值,并且在返回前还会将所匹配的key对应的entry调整在列表中的顺序(LinkedHashMap使用双链表来保存数据),让它处于链表的最后。当然,这种情况必须是在LinkedHashMap中accessOrder==true的情况下才生效的,反之就是get()方法不会改变被匹配的key对应的entry在列表中的位置,这样将大大方便了缓存顺序的调整和缓存的移除。这就是为什么要使用LinkedHashMap的原因。
@Override
public V get(Object key) {
/* * This method is overridden to eliminate the need for a polymorphic * invocation in superclass at the expense of code duplication. */
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);//调整顺序
return e.value;
}
}
return null;
}
好了,LruCache就介绍到这里,下面有个使用例子,大家可以看看:
package com.king.imagechooser.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/** * 本地图片加载框架(读取本地图片) * Created by King on 2016/3/21. */
public class ImageLoader {
/** * 图片缓存 */
private LruCache<String, Bitmap> mCaches;
/** * 线程池 */
private ExecutorService mThreadPool;
/** * 默认线程数量 */
private final static int DEFAULT_THREAD_COUNTS = 1;
/** * 信号量 */
private Semaphore mSemaphore = new Semaphore(0);
private Semaphore mSemaphoreThreadPool;
private Type mType = Type.LIFO;
public enum Type {
FIFO, LIFO
}
private LinkedList<Runnable> mTaskQueue;
private Thread mPoolThread;
private Handler mPoolThreadHandler;
private Handler mUIHandler;
/** * 单例 */
private static ImageLoader mInstance;
private ImageLoader(int treadCount, Type type) {
init(treadCount, type);
}
public static ImageLoader getInstance(int treadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(treadCount, type);
}
}
}
return mInstance;
}
/** * 初始化 * * @param threadCount * @param type */
public void init(int threadCount, Type type) {
mPoolThread = new Thread() {
@Override
public void run() {
Looper.prepare();
mPoolThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//从线程池取一个任务去执行
mThreadPool.execute(getTaskThread());
try {
//最多执行threadCount个线程,利用信号量来控制
mSemaphoreThreadPool.acquire();
} catch (Exception e) {
Log.e("King", e.getMessage());
}
}
};
//释放信号量
mSemaphore.release();
Looper.loop();
}
};
mPoolThread.start();
//获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheMemory = maxMemory / 4;
//初始化LruCache
mCaches = new LruCache<String, Bitmap>(cacheMemory) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
//创建线程池
mThreadPool = Executors.newFixedThreadPool(threadCount);
mTaskQueue = new LinkedList<Runnable>();
mType = type;
mSemaphoreThreadPool = new Semaphore(threadCount);
}
/** * 显示图片到ImageView */
public void loadImage(final String path, final ImageView imageView) {
imageView.setTag(path);
if (mUIHandler == null) {
mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//获取图片,为ImageView回调设置图片
ImageHolder holder = (ImageHolder) msg.obj;
Bitmap bm = holder.bitmap;
ImageView iv = holder.imageView;
String path = holder.path;
if (iv.getTag().toString().equals(path)) {
iv.setImageBitmap(bm);
}
}
};
}
Bitmap bitmap = getBitmapFromCache(path);
if (bitmap != null) {
refreshBitmap(path, imageView, bitmap);
} else {
addTaskThread(new Runnable() {
@Override
public void run() {
//获取imageView大小
ImageSize imageSize = getImageViewSize(imageView);
//压缩图片
Bitmap bitmap = decodeBitmapFromPath(path, imageSize.width, imageSize.height);
//将图片加入缓存
addBitmapToCache(path, bitmap);
//发送消息,更新视图
refreshBitmap(path, imageView, bitmap);
//释放信号量,保证剩余线程继续执行
mSemaphoreThreadPool.release();
}
});
}
}
private void refreshBitmap(String path, ImageView imageView, Bitmap bitmap) {
ImageHolder holder = new ImageHolder();
holder.bitmap = bitmap;
holder.imageView = imageView;
holder.path = path;
Message msg = Message.obtain();
msg.obj = holder;
mUIHandler.sendMessage(msg);
}
/** * 获取ImageView的实际大小 */
protected ImageSize getImageViewSize(ImageView imageView) {
ImageSize size = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width; //获取ImageView在layout中声明的宽度
}
if (width <= 0) {
width = getImageViewFieldValue(imageView, "mMaxWidth");//获取ImageView在layout中的最大宽度
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if (height <= 0) {
height = lp.height; //获取ImageView在layout中声明的高度
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight"); //获取ImageView在layout中的最大高度
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
size.width = width;
size.height = height;
return size;
}
//通过反射获取ImageView的属性(为了兼容低版本Api)
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = field.getInt(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (NoSuchFieldException e) {
Log.e("King", e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return value;
}
/** * 压缩图片 * * @param path * @param width * @param height * @return */
protected Bitmap decodeBitmapFromPath(String path, int width, int height) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, width, height);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
} catch (Throwable tr) {
Log.e("King", tr.getMessage());
}
return null;
}
// 计算图片的缩放值
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
try {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width
/ (float) reqWidth);
inSampleSize = Math.max(heightRatio, widthRatio);
}
return inSampleSize;
} catch (Throwable e) {
Log.e("King", e.getMessage());
}
return 1;
}
/** * 将图片添加到缓存 * * @param path * @param bm */
private void addBitmapToCache(String path, Bitmap bm) {
if (getBitmapFromCache(path) == null) {
if (bm != null) {
mCaches.put(path, bm);
}
}
}
//添加线程到集合中,方便管理
private synchronized void addTaskThread(Runnable runnable) {
try {
mTaskQueue.add(runnable);
if (mPoolThreadHandler == null) {
//请求信号量,保证Handler为空时再请求,从而避免重复请求
mSemaphore.acquire();
}
mPoolThreadHandler.sendEmptyMessage(0x110);
} catch (Throwable tr) {
Log.e("King", tr.getMessage(), tr);
}
}
/** * 获取线程 * * @return */
private Runnable getTaskThread() {
if (mType == Type.FIFO) {
return mTaskQueue.removeFirst();
} else if (mType == Type.LIFO) {
return mTaskQueue.removeLast();
}
return null;
}
/** * 从缓存中获取图片 */
public Bitmap getBitmapFromCache(String key) {
return mCaches.get(key);
}
private class ImageHolder {
Bitmap bitmap;
ImageView imageView;
String path;
}
private class ImageSize {
int width;
int height;
}
}