Picasso是Android中常用的圖片加載框架,本文注重解析其緩存邏輯碧囊。如果你沒有使用過picasso咖楣,請簡單的看下它的主頁瑟匆。
清空緩存
項目中經(jīng)常需要你手動清空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使用兩級緩存模型窃躲,內(nèi)存緩存及硬盤緩存变秦。
Picasso常用的方法為:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
內(nèi)存緩存
我們看下當picasso試圖加載圖片利用內(nèi)存緩存的邏輯 (以下邏輯摘自上面into(imageView)方法),其中 quickMemoryCacheCheck() 就是對內(nèi)存緩存進行搜索框舔,命中則使用內(nèi)存中的緩存:
// 根據(jù)內(nèi)存設定蹦玫,如果啟用內(nèi)存緩存則當命中時使用內(nèi)存圖片
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;
}
}
繼續(xù)深入這個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的內(nèi)存大小為~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);
}
它的內(nèi)存緩存鍵值可以看出設計者的idea,它使用的是url和顯示imageView所要求的屬性等纬凤。因為即使是同一張圖片福贞,在不同大小/效果的imageView所使用的bitmap也是不同的。以下是鍵值的例子:
http://i.imgur.com/DAl0KB8.jpg\nresize:540x540
所以在清空一個url對應的內(nèi)存緩存時的邏輯時這樣的(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);
}
}
}
以上是內(nèi)存緩存邏輯挖帘,以下是硬盤緩存邏輯。因為硬盤緩存實際是控制在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
具體的網(wǎng)絡請求邏輯各位可以到github上看下OkHttpDownloader.java (根據(jù)不同版本的okhttp依賴Downloader類可能不一樣) 這個文件的load()方法要拂。而在初始化OkHttpDownloader時,picasso創(chuàng)建了一個自己的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及圖片屬性在內(nèi)存緩存中存在拉一,如果存在讀取內(nèi)存
- 如果不命中內(nèi)存緩存采盒,加載圖片到disk和memory中。
- 內(nèi)存和硬盤緩存相互獨立舅踪。
感謝閱讀纽甘,歡迎指正良蛮!
注:全文使用的代碼片段均來自picasso 2.5.2版本抽碌。
2016/10/9日更新:更新清除硬盤緩存的代碼,以友好的方式獲取cache對象决瞳。當時看的代碼是master上货徙,所以是沒有okhttp3downloader的,不過2.5.2版本上的okhttpdownloader硬盤緩存的邏輯時一樣的皮胡,所以無大礙痴颊。