Picasso 内部缓存

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的缓存归纳如下:

  • 加载图片的url及图片属性在内存缓存中存在,如果存在读取内存
  • 如果不命中内存缓存,加载图片到disk和memory中。
  • 内存和硬盘缓存相互独立。

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容