1 Glide緩存與復(fù)用機(jī)制簡介
1.1 Glide的資源狀態(tài)可以分為四種
- Active Resources:有其他View正在展示這張圖片
- Memory cache:該圖片被存進(jìn)內(nèi)存中
- Resource:經(jīng)過decode蚌堵、transformed后的緩存
- Data:原始的沒有修改過的數(shù)據(jù)緩存
Glide讀取緩存也是依次從上面四種狀態(tài)的緩存中讀取,如果都未能找到圖片,則Glide會(huì)返回到原始資源以取回?cái)?shù)據(jù)(原始文件肢扯,Uri, Url等)
1.2 Glide中Bitmap復(fù)用機(jī)制
復(fù)用:
將已經(jīng)不需要使用的數(shù)據(jù)空間重新拿來使用,減少內(nèi)存抖動(dòng)(指在短時(shí)間內(nèi)有大量的對象被創(chuàng)建或者被回收的現(xiàn)象)
原理:
inMutable是Glide能夠復(fù)用Bitmap的基石,是BitmapFactory提供的一個(gè)參數(shù),表示該Bitmap是可變的我注,支持復(fù)用的。BitmapFactory.Options中提供了兩個(gè)屬性:inMutable迟隅、inBitmap但骨。當(dāng)進(jìn)行Bitmap復(fù)用時(shí),需要設(shè)置inMutable
為true智袭,inBitmap
設(shè)置被復(fù)用的已經(jīng)存在的Bitmap奔缠。Bitmap復(fù)用池使用LRU算法實(shí)現(xiàn)。
Bitmap復(fù)用使用條件:
- 在Android 4.4之前吼野,僅支持相同大小的Bitmap校哎,inSampleSize必須為1,而且必須采用jpeg或png格式瞳步。
- 在Android 4.4之后只有一個(gè)限制闷哆,就是被復(fù)用的Bitmap尺寸要大于 新的bitmap,簡單來說就是大圖可以給小圖復(fù)用单起。
2 緩存源碼流程
在Glide源碼分析-網(wǎng)絡(luò)圖片加載主流程分析一文中抱怔,我們已經(jīng)知道m(xù)emory cache和disk cache在Glide創(chuàng)建的時(shí)候也被創(chuàng)建了,Glide創(chuàng)建的代碼在GlideBuilder.build(Context)
方法
@NonNull
Glide build(@NonNull Context context) {
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
...);
}
return new Glide(
...
memoryCache,
...);
}
2.1 memoryCache
通過代碼可以看到 memoryCache 被放入 Engine 和 Glide 實(shí)例中嘀倒。在Engine中利用memoryCache進(jìn)行存取操作屈留,Glide 實(shí)例中的memoryCache是用來在內(nèi)存緊張的時(shí)候,通知memoryCache釋放內(nèi)存测蘑。Glide實(shí)現(xiàn)了ComponentCallbacks2接口灌危,在Glide創(chuàng)建完成后,通過applicationContext.registerComponentCallbacks(glide)
似的 Glide 實(shí)例可以監(jiān)聽內(nèi)存緊張的信號帮寻。
// Glide
@Override
public void onTrimMemory(int level) {
trimMemory(level);
}
public void trimMemory(int level) {
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
// memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
memoryCache.trimMemory(level);
bitmapPool.trimMemory(level);
arrayPool.trimMemory(level);
}
memoryCache是一個(gè)使用LRU(least recently used)算法實(shí)現(xiàn)的內(nèi)存緩存類LruResourceCache,繼承至LruCache
類赠摇,并實(shí)現(xiàn)了MemoryCache
接口固逗。LruCache定義了LRU算法實(shí)現(xiàn)相關(guān)的操作浅蚪,而MemoryCache定義的是內(nèi)存緩存相關(guān)的操作。
LruCache 的實(shí)現(xiàn)是利用了 LinkedHashMap 的這種數(shù)據(jù)結(jié)構(gòu)的一個(gè)特性( accessOrder=true 基于訪問順序 )再加上對 LinkedHashMap 的數(shù)據(jù)操作上鎖實(shí)現(xiàn)的緩存策略烫罩。
當(dāng)調(diào)用 put()方法時(shí)惜傲,就會(huì)在集合中添加元素,并調(diào)用
trimToSize()判斷緩存是否已滿贝攒,如果滿了就用 LinkedHashMap 的迭代器刪除隊(duì)尾元素盗誊,即近期最少訪問的元素。
當(dāng)調(diào)用 get()方法訪問緩存對象時(shí)隘弊,就會(huì)調(diào)用 LinkedHashMap 的 get()方法獲得對應(yīng)集合元素哈踱,同時(shí)會(huì)更新該元素到隊(duì)頭。
2.2 diskCacheFactory
diskCacheFactory是創(chuàng)建DiskCache的Factory梨熙,DiskCache接口定義
public interface DiskCache {
interface Factory {
/** 250 MB of cache. */
int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
@Nullable
DiskCache build();
}
interface Writer {
boolean write(@NonNull File file);
}
@Nullable
File get(Key key);
void put(Key key, Writer writer);
@SuppressWarnings("unused")
void delete(Key key);
void clear();
}
接著再來看下DiskCache.Factory的默認(rèn)實(shí)現(xiàn):InternalCacheDiskCacheFactory
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
public InternalCacheDiskCacheFactory(Context context) {
this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,
DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);
}
public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {
this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);
}
public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
long diskCacheSize) {
super(new CacheDirectoryGetter() {
@Override
public File getCacheDirectory() {
File cacheDirectory = context.getCacheDir();
if (cacheDirectory == null) {
return null;
}
if (diskCacheName != null) {
return new File(cacheDirectory, diskCacheName);
}
return cacheDirectory;
}
}, diskCacheSize);
}
}
由以上代碼可以看出:默認(rèn)會(huì)創(chuàng)建一個(gè)250M的緩存目錄开镣,其路徑為/data/data/{package}/cache/image_manager_disk_cache/
繼續(xù)看其父類DiskLruCacheFactory的代碼
public class DiskLruCacheFactory implements DiskCache.Factory {
private final long diskCacheSize;
private final CacheDirectoryGetter cacheDirectoryGetter;
public interface CacheDirectoryGetter {
File getCacheDirectory();
}
...
public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long diskCacheSize) {
this.diskCacheSize = diskCacheSize;
this.cacheDirectoryGetter = cacheDirectoryGetter;
}
@Override
public DiskCache build() {
File cacheDir = cacheDirectoryGetter.getCacheDirectory();
if (cacheDir == null) {
return null;
}
if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
return null;
}
return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
}
}
DiskLruCacheFactory.build()方法會(huì)返回一個(gè)DiskLruCacheWrapper類的實(shí)例,看下DiskLruCacheWrapper的實(shí)現(xiàn)
public class DiskLruCacheWrapper implements DiskCache {
private static final String TAG = "DiskLruCacheWrapper";
private static final int APP_VERSION = 1;
private static final int VALUE_COUNT = 1;
private static DiskLruCacheWrapper wrapper;
private final SafeKeyGenerator safeKeyGenerator;
private final File directory;
private final long maxSize;
private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
private DiskLruCache diskLruCache;
@SuppressWarnings("deprecation")
public static DiskCache create(File directory, long maxSize) {
return new DiskLruCacheWrapper(directory, maxSize);
}
@Deprecated
@SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
protected DiskLruCacheWrapper(File directory, long maxSize) {
this.directory = directory;
this.maxSize = maxSize;
this.safeKeyGenerator = new SafeKeyGenerator();
}
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
File result = null;
try {
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
...
}
return result;
}
@Override
public void put(Key key, Writer writer) {
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
try {
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
...
DiskLruCache.Editor editor = diskCache.edit(safeKey);
...
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
...
}
} finally {
writeLocker.release(safeKey);
}
}
...
}
顧名思義咽扇,里面包裝了一個(gè)DiskLruCache
邪财,該類主要是為DiskLruCache提供了一個(gè)根據(jù)Key生成safeKey的SafeKeyGenerator以及寫鎖DiskCacheWriteLocker。
回到GlideBuilder.build(Context)
中质欲,diskCacheFactory會(huì)被傳進(jìn)Engine中树埠,在Engine的構(gòu)造方法中會(huì)被包裝成為一個(gè)LazyDiskCacheProvider,在被需要的時(shí)候調(diào)用getDiskCache()方法嘶伟,這樣就會(huì)調(diào)用factory的build()方法返回一個(gè)DiskCache怎憋。代碼如下:
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
private final DiskCache.Factory factory;
private volatile DiskCache diskCache;
LazyDiskCacheProvider(DiskCache.Factory factory) {
this.factory = factory;
}
...
@Override
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
LazyDiskCacheProvider會(huì)在Engine后面的初始化流程中作為入?yún)鞯紻ecodeJobFactory的構(gòu)造器。在DecodeJobFactory創(chuàng)建DecodeJob時(shí)也會(huì)作為入?yún)?huì)傳進(jìn)去奋早,DecodeJob中會(huì)以全局變量保存此LazyDiskCacheProvider盛霎,在資源加載完畢并展示后,會(huì)進(jìn)行緩存的存儲(chǔ)耽装。同時(shí)愤炸,DecodeJob也會(huì)在DecodeHelper初始化時(shí),將此DiskCacheProvider設(shè)置進(jìn)去掉奄,供ResourceCacheGenerator规个、DataCacheGenerator讀取緩存,供SourceGenerator寫入緩存姓建。
2.3 ActiveResources
ActiveResources在Engine的構(gòu)造器中被創(chuàng)建诞仓,在ActiveResources的構(gòu)造器中會(huì)啟動(dòng)一個(gè)后臺(tái)優(yōu)先級級別(THREAD_PRIORITY_BACKGROUND)的線程,在該線程中會(huì)調(diào)用cleanReferenceQueue()方法一直循環(huán)清除ReferenceQueue中的將要被GC的Resource速兔。
final class ActiveResources {
private final boolean isActiveResourceRetentionAllowed;
private final Executor monitorClearedResourcesExecutor;
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
private volatile boolean isShutdown;
ActiveResources(boolean isActiveResourceRetentionAllowed) {
this(
isActiveResourceRetentionAllowed,
java.util.concurrent.Executors.newSingleThreadExecutor(
new ThreadFactory() {
@Override
public Thread newThread(@NonNull final Runnable r) {
return new Thread(
new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
r.run();
}
},
"glide-active-resources");
}
}));
}
@VisibleForTesting
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
@SuppressWarnings("WeakerAccess")
@Synthetic void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
// This section for testing only.
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
先來看看ActiveResources的activate方法(保存)墅拭、deactivate方法(刪除)的方法
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
activate方法會(huì)將參數(shù)封裝成為一個(gè)ResourceWeakReference,然后放入map中涣狗,如果對應(yīng)的key之前有值谍婉,那么調(diào)用之前值的reset方法進(jìn)行清除舒憾。deactivate方法先在map中移除,然后調(diào)用resource的reset方法進(jìn)行清除穗熬。ResourceWeakReference繼承WeakReference镀迂,內(nèi)部只是保存了Resource的一些屬性。
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
@SuppressWarnings("WeakerAccess") @Synthetic final Key key;
@SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;
@Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;
@Synthetic
@SuppressWarnings("WeakerAccess")
ResourceWeakReference(
@NonNull Key key,
@NonNull EngineResource<?> referent,
@NonNull ReferenceQueue<? super EngineResource<?>> queue,
boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource()) : null;
isCacheable = referent.isCacheable();
}
}
構(gòu)造方法中調(diào)用了super(referent, queue)
唤蔗,這樣做可以讓將要被GC的對象放入到ReferenceQueue中探遵。而ActiveResources.cleanReferenceQueue()方法會(huì)一直嘗試從queue中獲取將要被GC的resource,然后調(diào)用cleanupActiveReference方法將resource從activeEngineResources中移除妓柜。cleanupActiveReference源碼如下:
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
synchronized (listener) {
synchronized (this) {
// 移除active資源
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
// 構(gòu)造新的 Resource
EngineResource<?> newResource =
new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
newResource.setResourceListener(ref.key, listener);
// 回調(diào)Engine的onResourceReleased方法
// 這會(huì)導(dǎo)致此資源從active變成memory cache狀態(tài)
listener.onResourceReleased(ref.key, newResource);
}
}
}
Engine實(shí)現(xiàn)了EngineResource.ResourceListener箱季,此處的listener就是Engine,最終會(huì)回調(diào)Engine.onResourceReleased
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
如果資源可以被緩存领虹,則緩存到 memory cache规哪,否則對資源進(jìn)行回收。
2.4 磁盤緩存讀取
了解了上述三種緩存后我們分析下緩存的存取代碼塌衰。我們看下
public synchronized <R> LoadStatus load(...) {
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(...);
DecodeJob<R> decodeJob =
decodeJobFactory.build(...);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
緩存需要根據(jù)EngineKey去存取诉稍,先看下EngineKey的構(gòu)造方法
EngineKey(
Object model,
Key signature,
int width
int height,
Map<Class<?>, Transformation<?>> transformations,
Class<?> resourceClass,
Class<?> transcodeClass,
Options options)
model
load方法傳的參數(shù)signature
BaseRequestOptions的成員變量,默認(rèn)會(huì)是EmptySignature.obtain()
在加載本地resource資源時(shí)會(huì)變成ApplicationVersionSignature.obtain(context)width最疆、height
如果沒有指定override(int size)杯巨,那么將得到view的sizetransformations
默認(rèn)會(huì)基于ImageView的scaleType設(shè)置對應(yīng)的四個(gè)Transformation;
如果指定了transform努酸,那么就基于該值進(jìn)行設(shè)置resourceClass
解碼后的資源服爷,如果沒有asBitmap、asGif获诈,一般會(huì)是ObjecttranscodeClass
最終要轉(zhuǎn)換成的數(shù)據(jù)類型仍源,根據(jù)as方法確定,加載本地res或者網(wǎng)絡(luò)URL舔涎,都會(huì)調(diào)用asDrawable笼踩,所以為Drawableoptions
如果沒有設(shè)置過transform,此處會(huì)根據(jù)ImageView的scaleType默認(rèn)指定一個(gè)option
所以亡嫌,在多次加載同一個(gè)model的過程中嚎于,只要上述任何一個(gè)參數(shù)有改變,都不會(huì)認(rèn)為是同一個(gè)key挟冠。
回到Engine.load方法于购,從緩存加載成功后的回調(diào)cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
可以看到:active狀態(tài)的資源和memory cache狀態(tài)的資源都是DataSource.MEMORY_CACHE,并且加載的資源都是 EngineResource 對象知染,該對象內(nèi)部采用了引用計(jì)數(shù)去判斷資源是否被釋放肋僧,如果引用計(jì)數(shù)為0,那么會(huì)調(diào)用listener.onResourceReleased(key, this)方法通知外界此資源已經(jīng)釋放了。這里的listener是ResourceListener類型的接口嫌吠,只有一個(gè)onResourceReleased(Key key, EngineResource<?> resource)
方法伪窖,Engine實(shí)現(xiàn)了該接口,此處的listener就是Engine居兆。在Engine.onResourceReleased方法中會(huì)判斷資源是否可緩存,可緩存則將此資源放入memory cache中竹伸,否則回收掉該資源泥栖,代碼如下:
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
// 從activeResources中移除
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
// 存入 MemoryCache
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
繼續(xù)回到Engine.load方法,先來看下active資源獲取的方法
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
// 設(shè)置skipMemoryCache(true)勋篓,則isMemoryCacheable為false吧享,跳過ActiveResources
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
// 命中緩存,引用計(jì)數(shù)+1
active.acquire();
}
return active;
}
繼續(xù)分析cached資源獲取的方法譬嚣,如果從active資源中沒有獲取到緩存钢颂,則繼續(xù)從內(nèi)存緩存中查找
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
// 設(shè)置skipMemoryCache(true),則isMemoryCacheable為false拜银,跳過ActiveResources
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
// 命中緩存殊鞭,引用計(jì)數(shù)+1
cached.acquire();
// 將此資源從memoryCache中移到activeResources中
activeResources.activate(key, cached);
}
return cached;
}
如果從memoryCache中獲取到資源則將此資源從memoryCache中移到activeResources中。第一次加載的時(shí)候activeResources和memoryCache中都沒有緩存的尼桶,后面繼續(xù)通過DecodeJob和EngineJob去加載資源操灿。DecoceJob實(shí)現(xiàn)了Runnable接口,然后會(huì)被EngineJob.start方法提交到對應(yīng)的線程池中去執(zhí)行泵督。在DecoceJob的run方法中趾盐,會(huì)依次從ResourceCacheGenerator和DataCacheGenerator中去取緩存數(shù)據(jù),當(dāng)這兩者都取不到的情況下小腊,會(huì)交給SourceGenerator加載網(wǎng)絡(luò)圖片或者本地資源救鲤。resource資源和data資源都是磁盤緩存中的資源。
先看下 ResourceCacheGenerator.startNext
@Override
public boolean startNext() {
// list里面只有一個(gè)GlideUrl對象
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
// 獲得了三個(gè)可以到達(dá)的registeredResourceClasses
// GifDrawable秩冈、Bitmap本缠、BitmapDrawable
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
throw new IllegalStateException(
"Failed to find any load path from " + helper.getModelClass() + " to "
+ helper.getTranscodeClass());
}
// 遍歷sourceIds中的每一個(gè)key、resourceClasses中每一個(gè)class漩仙,以及其他的一些值組成key
// 嘗試在磁盤緩存中以key找到緩存文件
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
// PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
// we only run until the first one succeeds, the loop runs for only a limited
// number of iterations on the order of 10-20 in the worst case.
// 構(gòu)造key
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
// 查找緩存文件
cacheFile = helper.getDiskCache().get(currentKey);
// 如果找到了緩存文件搓茬,循環(huán)條件則會(huì)為false,退出循環(huán)
if (cacheFile != null) {
sourceKey = sourceId;
// 1. 找出注入時(shí)以File.class為modelClass的注入代碼
// 2. 調(diào)用所有注入的factory.build方法得到ModelLoader
// 3 .過濾掉不可能處理model的ModelLoader
// 此時(shí)的modelLoaders值為:
// [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader]
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
// 如果找到了緩存文件队他,hasNextModelLoader()方法則會(huì)為true卷仑,可以執(zhí)行循環(huán)
// 沒有找到緩存文件,則不會(huì)進(jìn)入循環(huán)麸折,會(huì)直接返回false
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
// 在循環(huán)中會(huì)依次判斷某個(gè)ModelLoader能不能加載此文件
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
// 如果某個(gè)ModelLoader可以锡凝,那么就調(diào)用其fetcher進(jìn)行加載數(shù)據(jù)
// 加載成功或失敗會(huì)通知自身
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
該方法的相關(guān)注釋代碼里都有標(biāo)明。找緩存時(shí)key的類型為ResourceCacheKey垢啼,我們先來看下ResourceCacheKey的構(gòu)成
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
ResourceCacheKey(
ArrayPool arrayPool,
Key sourceKey,
Key signature,
int width,
int height,
Transformation<?> appliedTransformation,
Class<?> decodedResourceClass,
Options options)
arrayPool
默認(rèn)值是LruArrayPool窜锯,不參與key的equals方法sourceKey
如果請求的是URL张肾,此處就是GlideUrl(GlideUrl implements Key)signature
BaseRequestOptions的成員變量,默認(rèn)會(huì)是EmptySignature.obtain()
在加載本地resource資源時(shí)會(huì)變成ApplicationVersionSignature.obtain(context)width锚扎、height
如果沒有指定override(int size)吞瞪,那么將得到view的sizeappliedTransformation
默認(rèn)會(huì)根據(jù)ImageView的scaleType設(shè)置對應(yīng)的BitmapTransformation;
如果指定了transform驾孔,那么就會(huì)是指定的值decodedResourceClass
可以被編碼成的資源類型芍秆,如BitmapDrawable等options
如果沒有設(shè)置過transform,此處會(huì)根據(jù)ImageView的scaleType默認(rèn)指定一個(gè)option
在ResourceCacheKey中翠勉,arrayPool并沒有參與equals方法妖啥。
生成ResourceCacheKey之后會(huì)根據(jù)key去磁盤緩存中查找cacheFile = helper.getDiskCache().get(currentKey);
helper.getDiskCache()返回DiskCache接口,它的實(shí)現(xiàn)類是DiskLruCacheWrapper对碌,看下DiskLruCacheWrapper.get方法
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
...
File result = null;
try {
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
...
}
return result;
}
這里調(diào)用SafeKeyGenerator生成了一個(gè)String類型的SafeKey荆虱,實(shí)際上就是對原始key中每個(gè)字段都使用SHA-256加密,然后將得到的字節(jié)數(shù)組轉(zhuǎn)換為16進(jìn)制的字符串朽们。生成SafeKey后怀读,接著根據(jù)SafeKey去DiskCache里面找對應(yīng)的緩存文件,然后返回文件骑脱。
回到ResourceCacheGenerator.startNext方法中愿吹,如果找到了緩存會(huì)調(diào)用loadData.fetcher.loadData(helper.getPriority(), this);
這里的 fetcher 是 ByteBufferFetcher,ByteBufferFetcher的loadData方法中最終會(huì)執(zhí)行callback.onDataReady(result)
這里callback是ResourceCacheGenerator
public void onDataReady(Object data) {
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
currentKey);
}
ResourceCacheGenerator的onDataReady方法又會(huì)回調(diào)DecodeJob的onDataFetcherReady方法進(jìn)行后續(xù)的解碼操作惜姐。
如果ResourceCacheGenerator沒有找到緩存犁跪,就會(huì)交給DataCacheGenerator繼續(xù)查找緩存。該類大體流程和ResourceCacheGenerator一樣歹袁,有點(diǎn)不同的是坷衍,DataCacheGenerator的構(gòu)造器有兩個(gè)構(gòu)造器,其中的DataCacheGenerator(List<Key>, DecodeHelper<?>, FetcherReadyCallback)構(gòu)造器是給SourceGenerator準(zhǔn)備的条舔。因?yàn)槿绻麤]有磁盤緩存枫耳,那么從源頭加載后,肯定需要進(jìn)行磁盤緩存操作的孟抗。所以迁杨,SourceGenerator會(huì)將加載后的資源保存到磁盤中,然后轉(zhuǎn)交給DataCacheGenerator從磁盤中取出交給ImageView展示凄硼。
看下DataCacheGenerator.startNext
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
...
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
...
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
這里的originalKey是DataCacheKey類型的铅协,DataCacheKey構(gòu)造方法如下
DataCacheKey(Key sourceKey, Key signature)
這里的sourceKey和signature與ResourceCacheKey中的兩個(gè)變量一致,從這里就可以看出:DataCache緩存的是原始的數(shù)據(jù)摊沉,ResourceCache緩存的是是被解碼狐史、轉(zhuǎn)換后的數(shù)據(jù)。
如果DataCacheGenerator沒有取到緩存,那么會(huì)交給SourceGenerator從源頭加載骏全〔园兀看下SourceGenerator的startNext方法
@Override
public boolean startNext() {
// 首次運(yùn)行dataToCache為null
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
// 首次運(yùn)行sourceCacheGenerator為null
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
加載成功后依然會(huì)回調(diào)SourceGenerator的onDataReady方法
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// cb 為 DecodeJob
cb.reschedule();
} else {
// cb 為 DecodeJob
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
先判斷獲取到的數(shù)據(jù)是否需要進(jìn)行磁盤緩存,如果需要磁盤緩存姜贡,則經(jīng)過DecodeJob试吁、EngineJob的調(diào)度,重新調(diào)用SourceGenerator.startNext方法楼咳,此時(shí)dataToCache
已經(jīng)被賦值潘悼,則會(huì)調(diào)用cacheData(data);
進(jìn)行磁盤緩存的寫入,并轉(zhuǎn)交給DataCacheGenerator完成后續(xù)的處理爬橡;否則就通知DecodeJob已經(jīng)加載成功。
先看下SourceGenerator的startNext方法中調(diào)用的SourceGenerator.cacheData(data)
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
helper.getDiskCache().put(originalKey, writer);
...
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
cacheData方法先構(gòu)建了一個(gè)DataCacheKey將data寫入了磁盤棒动,然后new了一個(gè)DataCacheGenerator賦值給sourceCacheGenerator糙申。回到startNext繼續(xù)向下執(zhí)行船惨,此時(shí)sourceCacheGenerator不為空柜裸,就調(diào)用其startNext()方法從磁盤中加載剛寫入磁盤的數(shù)據(jù),并返回true讓DecodeJob停止嘗試獲取數(shù)據(jù)粱锐。此時(shí)疙挺,從磁盤緩存中讀取數(shù)據(jù)的邏輯已經(jīng)完成,接下來是寫磁盤緩存怜浅。
假如SourceGenerator的onDataReady方法中的磁盤緩存策略不可用铐然,則會(huì)回調(diào)DecodeJob.onDataFetcherReady方法
// DecodeJob
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
private void decodeFromRetrievedData() {
...
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
decodeFromRetrievedData();
后續(xù)的方法調(diào)用鏈在之前的文章中分析過,主要做的事情就是:將原始的data數(shù)據(jù)轉(zhuǎn)變?yōu)榭梢怨㊣mageView顯示的resource數(shù)據(jù)并將其顯示在ImageView上恶座。
將原始的data數(shù)據(jù)轉(zhuǎn)變?yōu)閞esource數(shù)據(jù)后搀暑,會(huì)調(diào)用DecodeJob.onResourceDecoded(dataSource, decoded)
@Synthetic
@NonNull
<Z> Resource<Z> onResourceDecoded(DataSource dataSource,
@NonNull Resource<Z> decoded) {
@SuppressWarnings("unchecked")
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
// 不是 resource cache時(shí)要transform
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
// TODO: Make this the responsibility of the Transformation.
if (!decoded.equals(transformed)) {
decoded.recycle();
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
switch (encodeStrategy) {
case SOURCE:
key = new DataCacheKey(currentSourceKey, signature);
break;
case TRANSFORMED:
key =
new ResourceCacheKey(
decodeHelper.getArrayPool(),
currentSourceKey,
signature,
width,
height,
appliedTransformation,
resourceSubClass,
options);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}
然后是此過程中的磁盤緩存過程,影響的因素有encodeStrategy跨琳、DiskCacheStrategy.isResourceCacheable。encodeStrategy根據(jù)resource數(shù)據(jù)的類型來判斷,如果是Bitmap或BitmapDrawable缩麸,那么就是TRANSFORMED跷睦;如果是GifDrawable,那么就是SOURCE溅潜。磁盤緩存策略默認(rèn)是DiskCacheStrategy.AUTOMATIC术唬。源碼如下:
public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return (isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE || dataSource == DataSource.LOCAL) && encodeStrategy == EncodeStrategy.TRANSFORMED;
}
public boolean decodeCachedResource() {
return true;
}
public boolean decodeCachedData() {
return true;
}
};
只有dataSource為DataSource.LOCAL且encodeStrategy為EncodeStrategy.TRANSFORMED時(shí),才允許緩存滚澜。也就是只有本地的resource數(shù)據(jù)為Bitmap或BitmapDrawable的資源才可以緩存碴开。
在DecodeJob.onResourceDecoded中會(huì)調(diào)用deferredEncodeManager.init(key, encoder, lockedResult);
去初始化deferredEncodeManager。
在DecodeJob的decodeFromRetrievedData();
中拿到resource數(shù)據(jù)后會(huì)調(diào)用notifyEncodeAndRelease(resource, currentDataSource)
利用deferredEncodeManager對象進(jìn)行磁盤緩存的寫入
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
...
// 通知回調(diào),資源已經(jīng)就緒
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
onEncodeComplete();
}
deferredEncodeManager.encode行磁盤緩存的寫入
// DecodeJob
private static class DeferredEncodeManager<Z> {
private Key key;
private ResourceEncoder<Z> encoder;
private LockedResource<Z> toEncode;
@Synthetic
DeferredEncodeManager() { }
// We just need the encoder and resource type to match, which this will enforce.
@SuppressWarnings("unchecked")
<X> void init(Key key, ResourceEncoder<X> encoder, LockedResource<X> toEncode) {
this.key = key;
this.encoder = (ResourceEncoder<Z>) encoder;
this.toEncode = (LockedResource<Z>) toEncode;
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
// 存入磁盤緩存
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
boolean hasResourceToEncode() {
return toEncode != null;
}
void clear() {
key = null;
encoder = null;
toEncode = null;
}
}
diskCacheProvider.getDiskCache()
獲取到DiskLruCacheWrapper潦牛,并調(diào)用DiskLruCacheWrapper的put寫入眶掌。DiskLruCacheWrapper在寫入的時(shí)候會(huì)使用到寫鎖DiskCacheWriteLocker,鎖對象由對象池WriteLockPool創(chuàng)建巴碗,寫鎖WriteLock實(shí)現(xiàn)是一個(gè)不公平鎖ReentrantLock朴爬。
在緩存寫入前,會(huì)判斷key對應(yīng)的value存不存在橡淆,若存在則不寫入召噩。緩存的真正寫入會(huì)由DataCacheWriter交給ByteBufferEncoder
和StreamEncoder
兩個(gè)具體類來寫入,前者負(fù)責(zé)將ByteBuffer寫入到文件逸爵,后者負(fù)責(zé)將InputStream寫入到文件具滴。
到目前為止,磁盤緩存的讀寫流程都已分析完成师倔。
2.5 內(nèi)存緩存:ActiveResource與MemoryCache讀取
回到DecodeJob.notifyEncodeAndRelease方法中构韵,經(jīng)過notifyComplete、EngineJob.onResourceReady趋艘、notifyCallbacksOfResult方法中疲恢。
在該方法中一方面會(huì)將原始的resource包裝成一個(gè)EngineResource,然后通過回調(diào)傳給Engine.onEngineJobComplete
@Override
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
// 設(shè)置資源的回調(diào)為自己瓷胧,這樣在資源釋放時(shí)會(huì)通知自己的回調(diào)方法
if (resource != null) {
resource.setResourceListener(key, this);
// 將資源放入activeResources中显拳,資源變?yōu)閍ctive狀態(tài)
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
// 將engineJob從Jobs中移除
jobs.removeIfCurrent(key, engineJob);
}
在這里會(huì)將資源放入activeResources中,資源變?yōu)閍ctive狀態(tài)搓萧。后面會(huì)使用Executors.mainThreadExecutor()調(diào)用SingleRequest.onResourceReady回調(diào)進(jìn)行資源的顯示杂数。在觸發(fā)回調(diào)前后各有一個(gè)地方會(huì)對engineResource進(jìn)行acquire()和release()操作,這兩個(gè)操作分別發(fā)生在notifyCallbacksOfResult()方法的incrementPendingCallbacks瘸洛、decrementPendingCallbacks()調(diào)用中
@Synthetic
void notifyCallbacksOfResult() {
ResourceCallbacksAndExecutors copy;
Key localKey;
EngineResource<?> localResource;
synchronized (this) {
...
engineResource = engineResourceFactory.build(resource, isCacheable);
...
hasResource = true;
copy = cbs.copy();
incrementPendingCallbacks(copy.size() + 1);
localKey = key;
localResource = engineResource;
}
listener.onEngineJobComplete(this, localKey, localResource);
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
decrementPendingCallbacks();
}
synchronized void incrementPendingCallbacks(int count) {
...
if (pendingCallbacks.getAndAdd(count) == 0 && engineResource != null) {
engineResource.acquire();
}
}
synchronized void decrementPendingCallbacks() {
...
int decremented = pendingCallbacks.decrementAndGet();
if (decremented == 0) {
if (engineResource != null) {
engineResource.release();
}
release();
}
}
private class CallResourceReady implements Runnable {
private final ResourceCallback cb;
CallResourceReady(ResourceCallback cb) {
this.cb = cb;
}
@Override
public void run() {
synchronized (EngineJob.this) {
if (cbs.contains(cb)) {
// Acquire for this particular callback.
engineResource.acquire();
callCallbackOnResourceReady(cb);
removeCallback(cb);
}
decrementPendingCallbacks();
}
}
}
CallResourceReady的run方法中也會(huì)調(diào)用engineResource.acquire()耍休,上面的代碼調(diào)用結(jié)束后,engineResource的引用計(jì)數(shù)為1货矮。engineResource的引用計(jì)數(shù)會(huì)在RequestManager.onDestory方法中最終調(diào)用SingleRequest.clear()方法羊精,SingleRequest.clear()內(nèi)部調(diào)用releaseResource()、Engine.release 進(jìn)行釋放囚玫,這樣引用計(jì)數(shù)就變?yōu)?喧锦。引用計(jì)數(shù)就變?yōu)?后會(huì)通知Engine將此資源從active狀態(tài)變成memory cache狀態(tài)。如果我們再次加載資源時(shí)可以從memory cache中加載抓督,那么資源又會(huì)從memory cache狀態(tài)變成active狀態(tài)燃少。也就是說,在資源第一次顯示后铃在,我們關(guān)閉頁面阵具,資源會(huì)由active變成memory cache碍遍;然后我們再次進(jìn)入頁面,加載時(shí)會(huì)命中memory cache阳液,從而又變成active狀態(tài)怕敬。
2.6 總結(jié)
memory cache和disk cache在Glide創(chuàng)建的時(shí)候也被創(chuàng)建。
disk cache默認(rèn)會(huì)創(chuàng)建一個(gè)250M的緩存目錄(/data/data/{package}/cache/image_manager_disk_cache/)帘皿。
ActiveResources在Engine的構(gòu)造器中被創(chuàng)建东跪,內(nèi)部維護(hù)了一個(gè) Map<Key, ResourceWeakReference>類型的activeEngineResources用來存儲(chǔ)包裹EngineResource的ResourceWeakReference,ResourceWeakReference構(gòu)造器中會(huì)傳入一個(gè)ReferenceQueue鹰溜,在ActiveResources的構(gòu)造器中會(huì)啟動(dòng)一個(gè)后臺(tái)線程虽填,在該線程中會(huì)循環(huán)從activeEngineResources清除ReferenceQueue中的將要被GC的Resource。
ActiveResources被引用后曹动,其內(nèi)部的引用計(jì)數(shù)會(huì)+1斋日,當(dāng)被釋放后,其內(nèi)部的引用計(jì)數(shù)會(huì)-1墓陈,當(dāng)引用計(jì)數(shù)為0恶守,則表示該ActiveResources不再被引用,會(huì)將資源放入LruResourceCache中跛蛋。
首先從ActiveResources中獲取緩存資源,獲取不到再從LruResourceCache中查找痊硕。第一次沒有緩存赊级,會(huì)從網(wǎng)絡(luò)下載圖片成功后會(huì)存入磁盤緩存,然后再從磁盤緩存獲取資源存入ActiveResources中并將其交給ImageView展示岔绸。
緩存查找中涉及的三個(gè)類:ResourceCacheGenerator理逊、DataCacheGenerator、SourceGenerator盒揉。按照先后關(guān)系依次調(diào)用他們的startNext()方法查找緩存晋被,ResourceCacheGenerator獲取downsample、transform后的資源文件的緩存文件刚盈;DataCacheGenerator獲取原始的沒有修改過的資源文件的緩存文件羡洛;SourceGenerator獲取原始源數(shù)據(jù)。
ActiveResources引用計(jì)數(shù)就變?yōu)?后會(huì)通知Engine將此資源從active狀態(tài)變成memory cache狀態(tài)藕漱。如果我們再次加載資源時(shí)可以從memory cache中加載欲侮,那么資源又會(huì)從memory cache狀態(tài)變成active狀態(tài)。也就是說肋联,在資源第一次顯示后威蕉,我們關(guān)閉頁面,資源會(huì)由active變成memory cache橄仍;然后我們再次進(jìn)入頁面韧涨,加載時(shí)會(huì)命中memory cache牍戚,從而又變成active狀態(tài)。
之所以需要ActiveResources虑粥,因?yàn)樗萌跻冒b資源如孝,隨時(shí)可能被回收,memory的強(qiáng)引用頻繁讀寫可能造成內(nèi)存激增頻繁GC舀奶,而造成內(nèi)存抖動(dòng)暑竟。資源在使用過程中保存在ActiveResources中,而ActiveResources是弱引用育勺,隨時(shí)被系統(tǒng)回收但荤,不會(huì)造成內(nèi)存過多使用和泄漏。
3 Bitmap復(fù)用池復(fù)用源碼流程
在Glide源碼分析-網(wǎng)絡(luò)圖片加載主流程分析文章中分析過Glide加載圖片主要流程源碼涧至,在ByteBufferBitmapDecoder.decode方法中腹躁,先將ByteBuffer轉(zhuǎn)換成InputStream,然后在調(diào)用Downsampler.decode方法進(jìn)行解碼南蓬,代碼如下:
// ByteBufferBitmapDecoder
@Override
public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
@NonNull Options options)
throws IOException {
InputStream is = ByteBufferUtil.toStream(source);
return downsampler.decode(is, width, height, options);
}
繼續(xù)跟進(jìn)Downsampler.decode方法
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
+ " mark()");
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
// getDefaultOptions()中將inMutable設(shè)置為true
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
// inTempStorage是一個(gè)bitmap解析的參數(shù)纺非,帶入一個(gè)buffer,創(chuàng)建臨時(shí)文件赘方,將圖片存儲(chǔ)的臨時(shí)緩存空間
bitmapFactoryOptions.inTempStorage = bytesForOptions;
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
在getDefaultOptions()方法中會(huì)將inMutable設(shè)置為true烧颖,代碼如下:
private static synchronized BitmapFactory.Options getDefaultOptions() {
BitmapFactory.Options decodeBitmapOptions;
synchronized (OPTIONS_QUEUE) {
decodeBitmapOptions = OPTIONS_QUEUE.poll();
}
if (decodeBitmapOptions == null) {
decodeBitmapOptions = new BitmapFactory.Options();
resetOptions(decodeBitmapOptions);
}
return decodeBitmapOptions;
}
private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
decodeBitmapOptions.inTempStorage = null;
decodeBitmapOptions.inDither = false;
decodeBitmapOptions.inScaled = false;
decodeBitmapOptions.inSampleSize = 1;
decodeBitmapOptions.inPreferredConfig = null;
decodeBitmapOptions.inJustDecodeBounds = false;
decodeBitmapOptions.inDensity = 0;
decodeBitmapOptions.inTargetDensity = 0;
decodeBitmapOptions.outWidth = 0;
decodeBitmapOptions.outHeight = 0;
decodeBitmapOptions.outMimeType = null;
decodeBitmapOptions.inBitmap = null;
decodeBitmapOptions.inMutable = true;
}
回到Downsampler.decode方法中,繼續(xù)調(diào)用decodeFromWrappedStreams方法返回Bitmap窄陡,跟進(jìn)decodeFromWrappedStreams
// DownSampler
private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
int requestedHeight, boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks) throws IOException {
long startTime = LogTime.getLogTime();
// 計(jì)算原始大小
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
String sourceMimeType = options.outMimeType;
...
// 計(jì)算方向
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
// 計(jì)算目標(biāo)大小
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
// 計(jì)算類型
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
...
// 使用復(fù)用池
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
...
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);
...
return rotated;
}
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
...
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
setInBitmap方法里面的bitmapPool就是LruBitmapPool炕淮,在Glide構(gòu)造器里面被初始化,LruBitmapPool 就是Glide提供Bitmap復(fù)用池,真正的實(shí)現(xiàn)類是LruPoolStrategy
public class LruBitmapPool implements BitmapPool {
private final LruPoolStrategy strategy;
...
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
bitmap.recycle();
return;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
dump();
evict();
}
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
result.eraseColor(Color.TRANSPARENT);
} else {
result = createBitmap(width, height, config);
}
return result;
}
}
看下LruBitmapPool.getDirty方法
// LruBitmapPool
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
// 優(yōu)先獲取
Bitmap result = getDirtyOrNull(width, height, config);
if (result == null) { // 若沒有跳夭,新建一個(gè)bitmap
result = createBitmap(width, height, config);
}
return result;
}
如果可以使用bitmap池涂圆,就會(huì)調(diào)用bitmapPool的getDirty(),最后賦值給inBitmap币叹,總結(jié)一下:setInBitmap方法主要就是從LruBitmapPool中獲取可以被復(fù)用的Bitmap返回润歉,并賦值給BitmapFactory.Options的inBitmap。
那么可以被復(fù)用的Bitmap是什么時(shí)候加入Bitmap復(fù)用池呢颈抚?當(dāng)Resource資源沒有被引用并且不可被緩存的時(shí)候踩衩,會(huì)調(diào)用recycle()方法進(jìn)行回收,在BitmapDrawableResource的recycle()方法被調(diào)用的時(shí)候贩汉,會(huì)將當(dāng)前BitmapDrawableResource的bitmap放入復(fù)用池九妈,代碼如下:
// BitmapDrawableResource
@Override
public void recycle() {
bitmapPool.put(drawable.getBitmap());
}
LruBitmapPool內(nèi)部利用了Lru算法,每次操作都自動(dòng)檢測是否刪除多余的緩存
// LruBitmapPool
private void evict() {
trimToSize(maxSize);
}
private synchronized void trimToSize(long size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}
再來看看Bitmap復(fù)用池對Bitmap具體的存取邏輯的類LruPoolStrategy雾鬼,在LruBitmapPool的put方法中會(huì)調(diào)用LruPoolStrategy的put方法萌朱,在LruBitmapPool的get方法中會(huì)調(diào)用getDirtyOrNull方法進(jìn)而調(diào)用LruPoolStrategy的get方法,LruPoolStrategy時(shí)一個(gè)接口策菜,在LruBitmapPool中為LruPoolStrategy類型的全局變量strategy
其賦值的地方在LruBitmapPool構(gòu)造器中
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
當(dāng)sdk版本在19之上晶疼,會(huì)創(chuàng)建SizeConfigStrategy實(shí)例并賦值給strategy酒贬,那么繼續(xù)看下SizeConfigStrategy的put和get方法相關(guān)代碼
public class SizeConfigStrategy implements LruPoolStrategy {
private static final int MAX_SIZE_MULTIPLE = 8;
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
// 存入
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig());
groupedMap.put(key, bitmap);
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
// 取出
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
// 查找出最合適的bitmap
Key bestKey = findBestKey(size, config);
// 取出
Bitmap result = groupedMap.get(bestKey);
if (result != null) {
// Decrement must be called before reconfigure.
decrementBitmapOfSize(bestKey.size, result);
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
}
return result;
}
}
findBestKey()方法就是通過對size進(jìn)行匹配,找出最合適size的Bitmap的key翠霍,上面有提到過:在Android 4.4之后復(fù)用Bitmap有一個(gè)限制锭吨,就是被復(fù)用的Bitmap尺寸要大于新的Bitmap尺寸,findBestKey()方法就是實(shí)現(xiàn)這個(gè)邏輯
private Key findBestKey(int size, Bitmap.Config config) {
Key result = keyPool.get(size, config);
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
// 返回大于或等于指定size的最小的符合要求的size
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result);
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
總結(jié)
- Glide中使用Bitmap復(fù)用池來減少內(nèi)存抖動(dòng)
- Bitmap復(fù)用池實(shí)現(xiàn)類是LruBitmapPool寒匙,通過LRU算法來管理Bitmap復(fù)用池
- 具體的存儲(chǔ)和取出符合要求的可被復(fù)用的Bitmap的邏輯在SizeConfigStrategy中零如,主要是通過NavigableMap數(shù)據(jù)結(jié)構(gòu)的ceilingKey(K key)方法取出大于或等于新的Bitmap的size的最優(yōu)size對應(yīng)的key,進(jìn)而取出最優(yōu)的待復(fù)用的Bitmap
參考鏈接
https://muyangmin.github.io/glide-docs-cn/doc/caching.html