Picasso源码阅读笔记七

525 阅读4分钟

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来控制的。