Android知识AndroidAndroid开发

Picasso 内部缓存

2016-09-25  本文已影响2919人  HunkDeng

Picasso是Android中常用的图片加载框架,本文注重解析其缓存逻辑。如果你没有使用过picasso,请简单的看下它的主页

清空缓存

项目中经常需要你手动清空picasso的缓存,比如用户更新了个人头像。如果你是带着相同问题找到这个帖子的话,那以下就是你的答案。(2016/10/9更新)

Picasso.with(context).invalidate(url);  // clear bitmap cache in memory
// 遍历disk cache 的url key,找出你想删的image url,然后remove掉。

// Picasso不提供getDownloader(),所以你得用以下方法绕过。
OkHttpDownloader okHttpDownloader = new OkHttpDownloader(context);
OkHttpClient client = ReflectionHelpers.getField(okHttpDownloader, "client");
picassoDiskCache = client.getCache(); // 拿到disk cache
Picasso picasso = new Picasso.Builder(context)
        .downloader(okHttpDownloader)
        .build();
Iterator<String> iterator = picassoDiskCache.urls()
while (iterator.hasNext()) {
   String url = iterator.next();
   if (imgUrl.equals(url)) {
     // remove disk cache
     iterator.remove();
     break;
   }
 }

加载图片的逻辑

首先Picasso使用两级缓存模型,内存缓存及硬盘缓存。
Picasso常用的方法为:

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

内存缓存

我们看下当picasso试图加载图片利用内存缓存的逻辑 (以下逻辑摘自上面into(imageView)方法),其中 quickMemoryCacheCheck() 就是对内存缓存进行搜索,命中则使用内存中的缓存:

// 根据内存设定,如果启用内存缓存则当命中时使用内存图片
if (shouldReadFromMemoryCache(memoryPolicy)) {
  Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
  if (bitmap != null) {
    picasso.cancelRequest(target);
    setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
    if (picasso.loggingEnabled) {
      log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
    }
    if (callback != null) {
      callback.onSuccess();
    }
    return;
  }
}

继续深入这个quickMemoryCacheCheck() 方法

Bitmap quickMemoryCacheCheck(String key) {
  Bitmap cached = cache.get(key);
  if (cached != null) {
    stats.dispatchCacheHit();
  } else {
    stats.dispatchCacheMiss();
  }
  return cached;
}

其实就是检查cache这个对象,picasso使用的是自己写的LRU缓存,逻辑和Android SDK的差不多。这个LruCache的内存大小为~15%

/** Create a cache using an appropriate portion of the available RAM as the maximum size. */
public LruCache(@NonNull Context context) {  
   this(Utils.calculateMemoryCacheSize(context));
}

// Utils.calculateMemoryCacheSize
static int calculateMemoryCacheSize(Context context) {
  ActivityManager am = getService(context, ACTIVITY_SERVICE);
  boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
  int memoryClass = am.getMemoryClass();
  if (largeHeap && SDK_INT >= HONEYCOMB) {
    memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
  }
  // Target ~15% of the available heap.
  return (int) (1024L * 1024L * memoryClass / 7);
}

它的内存缓存键值可以看出设计者的idea,它使用的是url和显示imageView所要求的属性等。因为即使是同一张图片,在不同大小/效果的imageView所使用的bitmap也是不同的。以下是键值的例子:

http://i.imgur.com/DAl0KB8.jpg\nresize:540x540

所以在清空一个url对应的内存缓存时的逻辑时这样的(Picasso.invlidate(url)):

/**
 * Invalidate all memory cached images for the specified {@code uri}.
 *
 * @see #invalidate(String)
 * @see #invalidate(File)
 */
public void invalidate(@Nullable Uri uri) {
  if (uri != null) {
    cache.clearKeyUri(uri.toString());
  }
}

// cache.clearKeyUri(uri.toString());
@Override public final synchronized void clearKeyUri(String uri) {
  int uriLength = uri.length();
  for (Iterator<Map.Entry<String, Bitmap>> i = map.entrySet().iterator(); i.hasNext();) {
    Map.Entry<String, Bitmap> entry = i.next();
    String key = entry.getKey();
    Bitmap value = entry.getValue();
    int newlineIndex = key.indexOf(KEY_SEPARATOR);
    // 如果对应的url相同,删除各种图片属性的bitmap缓存
    if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {
      i.remove();
      size -= Utils.getBitmapBytes(value);
    }
  }
}  

以上是内存缓存逻辑,以下是硬盘缓存逻辑。因为硬盘缓存实际是控制在okhttp中,这里我们就简单的讲下picasso是怎么自定义它的硬盘缓存的。

硬盘缓存

依然从into(imageView)开始

Action action =
    new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
        requestKey, tag, errorResId);
picasso.enqueueAndSubmit(action);

在picasso.enqueueAndSubmit()中,跟踪几层逻辑后,其实用的是picasso的downloader去下载响应的图片。而当服务器返回的头中有缓存头的信息的话(比如cache-control等),okhttp就会为我们缓存它。http头样例:

cache-control: public, max-age=31536000

具体的网络请求逻辑各位可以到github上看下OkHttpDownloader.java (根据不同版本的okhttp依赖Downloader类可能不一样) 这个文件的load()方法。而在初始化OkHttpDownloader时,picasso创建了一个自己的disk cache object,大小是~2%的硬盘空间。默认路径是data/data/your package name/cache/picasso-cache/。

  /** Create new downloader that uses OkHttp. This will install an image cache into your application
   * cache directory.
   */
  public OkHttpDownloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }

  /**
   * Create new downloader that uses OkHttp. This will install an image cache into the specified
   * directory.
   *
   * @param cacheDir The directory in which the cache should be stored
   */
  public OkHttpDownloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  /**
   * Create new downloader that uses OkHttp. This will install an image cache into your application
   * cache directory.
   *
   * @param maxSize The size limit for the cache.
   */
  public OkHttpDownloader(final Context context, final long maxSize) {
    this(Utils.createDefaultCacheDir(context), maxSize);
  }

  @TargetApi(JELLY_BEAN_MR2)
  static long calculateDiskCacheSize(File dir) {
    long size = MIN_DISK_CACHE_SIZE;

    try {
      StatFs statFs = new StatFs(dir.getAbsolutePath());
      //noinspection deprecation
      long blockCount =
          SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockCount() : statFs.getBlockCountLong();
      //noinspection deprecation
      long blockSize =
          SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockSize() : statFs.getBlockSizeLong();
      long available = blockCount * blockSize;
      // Target 2% of the total space.
      size = available / 50;
    } catch (IllegalArgumentException ignored) {
    }

    // Bound inside min/max size for disk cache.
    return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
  }

这个磁盘缓存是DiskLruCache。至此关于picasso的缓存归纳如下:

感谢阅读,欢迎指正!
注:全文使用的代码片段均来自picasso 2.5.2版本。

2016/10/9日更新:更新清除硬盘缓存的代码,以友好的方式获取cache对象。当时看的代码是master上,所以是没有okhttp3downloader的,不过2.5.2版本上的okhttpdownloader硬盘缓存的逻辑时一样的,所以无大碍。

上一篇 下一篇

猜你喜欢

热点阅读