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硬盘缓存的逻辑时一样的,所以无大碍。
网友评论