Picasso提供了内存缓存和磁盘缓存。
内存缓存策略
Picasso的枚举类MemoryPolicy提供了两种内存缓存策略: NO_CACHE:不读取内存缓存。 NO_STORE:不写入内存缓存,避免一次性请求导致其他图片缓存被回收。
内存缓存的读取
//BitmapHunter的hunt方法是Picasso获取图片的主要逻辑
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//根据内存策略判断是否读取内存缓存。
//如果读取且找到要求的Bitmap则返回结果。
//否则调用RequestHandler的load方法获取图片。
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
...
}
内存缓存的写入
当以异步方式成功获取到图片时,会调用Dispatcher的performComplete方法根据缓存策略把图片写入内存缓存。同步方式则不会自动缓存图片。
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
...
}
内存缓存key的生成
Picasso用一个Map来作为内存缓存的数据结构。Picasso根据请求类Request生成key来保存变换后的图片。
static String createKey(Request data, StringBuilder builder) {
if (data.stableKey != null) {//stableKey可以通过RequestCreator的stableKey方法指定
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
builder.append(KEY_SEPARATOR);
//Picasso是缓存变换后的Bitmap
if (data.rotationDegrees != 0) {
builder.append("rotation:").append(data.rotationDegrees);
if (data.hasRotationPivot) {
builder.append('@').append(data.rotationPivotX).append('x').append(datarotationPivotY);
}
builder.append(KEY_SEPARATOR);
}
if (data.hasSize()) {
builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
builder.append(KEY_SEPARATOR);
}
if (data.centerCrop) {
builder.append("centerCrop").append(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}
if (data.transformations != null) {
for (int i = 0, count = data.transformations.size(); i < count; i++) {
builder.append(data.transformations.get(i).key());
builder.append(KEY_SEPARATOR);
}
}
return builder.toString();
}
内存缓存的实现
Picasso定义了内存缓存接口Cache。
//用于存储最近频繁使用的图片的内存缓存
//由于内存缓存是被多线程使用的,所以必须确保该接口的set和get方法的实现是线程安全的。
public interface Cache {
Bitmap get(String key);//获取Bitmap
void set(String key, Bitmap bitmap);//缓存Bitmap
int size();//当前内存缓存的大小,单位是字节
int maxSize();//内存缓存所能容纳的大小,单位是字节
void clear();//清空内存缓存
void clearKeyUri(String keyPrefix);//清除key的前缀是keyPrefix的Bitmap
}
Picasso提供了一个Cache实现Cache.NONE,它不保存任何数据的。
Picasso默认是使用LruCache作为内存缓存的。LruCache是根据Lru策略进行缓存的,即最近最少使用算法。
LruCache
LruCache使用了LinkedHashMap来保存数据。当把LinkedHashMap的构造参数accessOrder设置为true时就可以实现Lru算法了。
Picasso默认取应用运行时的七分之一的可用堆内存作为内存缓存的容量。
//Picasso使用该构造函数创建内存缓存
//取合适比例的可用堆内存作为最大容量
public LruCache(Context context) {
this(Utils.calculateMemoryCacheSize(context));
}
//根据给定的最大容量创建内存缓存
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("Max size must be positive.");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
}
//计算合适的最大容量
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = getService(context, ACTIVITY_SERVICE);
//判断manifest中是否设置了android:largeHeap=true
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = am.getMemoryClass();
if (largeHeap && SDK_INT >= HONEYCOMB) {
//如果设置了android:largeHeap=true且api>11,则调用getLargeMemoryClass方法获取运行时可用堆内存大小,否则调用getMemoryClass方法
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
}
//取可用堆内存的7分之一
return 1024 * 1024 * memoryClass / 7;
}
LruCache的方法都用synchronized来保证线程安全。
@Override public Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {//用synchronized保证线程安全
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;//获取缓存成功计算加1
return mapValue;
}
missCount++;//获取缓存失败计数加1
}
return null;
}
@Override public void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}
Bitmap previous;
synchronized (this) {//用synchronized保证线程安全
putCount++;//set方法调用次数加1
size += Utils.getBitmapBytes(bitmap);//计数并加上要缓存的图片大小
previous = map.put(key, bitmap);//把key和图片放进Map中
if (previous != null) {//如果之前key有对应的图片要减掉之前的图片大小
size -= Utils.getBitmapBytes(previous);
}
}
//移除最旧的图片缓存来保证缓存大小不超过最大容量
trimToSize(maxSize);
}
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {//用synchronized保证线程安全
//如果当前缓存大小小于0或者Map为空而size不为0,则缓存状态不一致,抛出异常
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
//当缓存大小小于最大容量或者Map为空,停止移除
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key); //移除最旧图片
size -= Utils.getBitmapBytes(value); //并且缓存大小减去图片大小
evictionCount++;//移除计数加1
}
}
}
public final void evictAll() {
trimToSize(-1); //清空缓存,把容量置为-1
}
磁盘缓存
Picasso的枚举类NetworkPolicy提供了三种磁盘缓存策略: NO_CACHE:不读缓存,直接发送网络请求。 NO_STORE:不进行磁盘缓存,这个只在使用OkHttp时才有效。 OFFLINE:只读取缓存。
磁盘缓存是通过Http请求的头部Cache-Control来控制的。