上一篇 Android 圖檔加載架構Glide主流程源碼分析
一個好使的圖檔加載架構,與它的緩存設計關系密切,這裡來通過源碼看看Glide是怎麼設計它的緩存的吧。
根據加載主流程我們可以知道Glide的使用是要先初始化Glide單例,進入到GlideBuilder
public final class GlideBuilder {
...
public Glide build(Context context) {
// 開始初始化glide
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}
...
}
這個方法挺長的,第10行,初始化操作磁盤緩存的線程池,
第18行,初始化記憶體大小,接着得到的memoryCache、bitmapPool、arrayPool分别傳入Engine、Glide執行個體中,
跟蹤看下18行,看下記憶體緩存大小怎麼計算的
public final class MemorySizeCalculator {
...
// Package private to avoid PMD warning.
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
this.context = builder.context;
arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
// 計算APP可申請最大使用記憶體,再乘以乘數因子,記憶體過低時乘以0.33,一般情況乘以0.4
int maxSize =
getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
// ARGB_8888 ,每個像素占用4個位元組記憶體
// 計算螢幕這麼大尺寸的圖檔占用記憶體大小
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
// 計算目标位圖池記憶體大小
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
// 計算目标Lrucache記憶體大小,也就是螢幕尺寸圖檔大小乘以2
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
// 最終APP可用記憶體大小
int availableSize = maxSize - arrayPoolSize;
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
// 如果目标位圖記憶體大小+目标Lurcache記憶體大小小于APP可用記憶體大小,則OK
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
} else {
// 否則用APP可用記憶體大小等比分别指派
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"Calculation complete"
+ ", Calculated memory cache size: "
+ toMb(memoryCacheSize)
+ ", pool size: "
+ toMb(bitmapPoolSize)
+ ", byte array size: "
+ toMb(arrayPoolSize)
+ ", memory class limited? "
+ (targetMemoryCacheSize + targetBitmapPoolSize > maxSize)
+ ", max size: "
+ toMb(maxSize)
+ ", memoryClass: "
+ builder.activityManager.getMemoryClass()
+ ", isLowMemoryDevice: "
+ isLowMemoryDevice(builder.activityManager));
}
}
...
}
注釋寫得比較清楚了,就不解釋了,那麼Glide在哪開始使用緩存的呢?
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
// 根據各種參數建立圖檔key
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);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// 檢查記憶體中Lrucache是否有目标圖檔
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 記憶體中沒有圖檔建構任務往下執行
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
...
}
Engine的load()方法還熟悉吧,很長一串
28-29,建立圖檔URL、寬、高等一系列參數建立key
31-39,從記憶體緩存弱引用中根據Key擷取圖檔資源,有就傳回,沒有往下執行
41-49,從記憶體緩存Lrucache中根絕key擷取圖檔資源,有就傳回,沒有往下執行
通過以上代碼可以看出Glide記憶體緩存采用了2級,第一級是弱引用,第二級才是Lrucache,如果軟引用中沒有
對應的圖檔緩存,就從Lrucache中擷取,如果還是沒有才去檢查磁盤緩存,如果還是沒有最後才去網絡下載下傳
這裡來看下擷取記憶體緩存圖檔資源的方法loadFromActiveResources()和loadFromCache(),
首先跟蹤loadFromActiveResources()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
...
}
final class ActiveResources {
...
@Nullable
EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
...
}
這裡是從記憶體緩存的弱引用中擷取的相應圖檔資源
第7行,判斷緩存是否開啟,如果可用才繼續進行,否則傳回null,預設是開啟的,我們也可以使用的時候關閉,如下
ImgurGlide.with(vh.imageView)
.load(image.link)
.skipMemoryCache(true)
.into(vh.imageView);
第10行,根據圖檔key擷取軟引用圖檔資源active,如果不為null,則調用active.acquire(),傳回相應資源
這裡要注意2個方法acquire()和release()
class EngineResource<Z> implements Resource<Z> {
...
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
}
/**
* Decrements the number of consumers using the wrapped resource. Must be called on the main
* thread.
*
* <p>This must only be called when a consumer that called the {@link #acquire()} method is now
* done with the resource. Generally external users should never call this method, the framework
* will take care of this for you.
*/
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
...
}
第10行和第28行,可以看到當調用acquire(),acquired這個成員變量會自增1,當調用release(),acquired這個
成員變量會自減1,當acquired數量大于0,說明目前EngineResource執行個體被使用中
全局搜尋可以知道目前資源加載結束後,會調用release(),看下29行,一個回調,找到實作的類Engine
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
// 從記憶體弱引用中移除圖檔
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
// 記憶體Lrucache中添加圖檔
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
第9行,從記憶體弱引用中移除圖檔
第12行,記憶體Lrucache中添加此圖檔
接下來看下loadFromCache()方法
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
@SuppressWarnings("unchecked")
private EngineResource<?> getEngineResourceFromCache(Key key) {
//cache 是LruResourceCache執行個體
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
...
}
第6行,判斷是否開啟記憶體緩存,預設是開啟的
10-15行,從記憶體Lrucache緩存中擷取key對應的圖檔資源并傳回
第12行,調用acquire(),同理目前資源調用數+1
第13行,跟蹤進入ActiveResources類
final class ActiveResources {
...
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
...
}
這裡把Lrucache緩存中的資源存入弱引用緩存
到這裡其實應該比較清晰了,Glide加載的記憶體緩存的圖檔資源始終是軟引用的,當引用中有則傳回資源,且資源存入
Lrucache中,如果沒有,從Lrucache中傳回存入弱引用中,如果Lrucache中也沒有呢?
以上就是Glide記憶體緩存的源碼分析,接下來開始磁盤緩存的分析
public abstract class DiskCacheStrategy {
...
public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
@Override
public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
...
}
磁盤緩存政策,DiskCacheStrategy這個類定義了幾種政策類型
DiskCacheStrategy.ALL :
表示既緩存原始圖檔,也緩存轉換過後的圖檔。對于遠端圖檔,緩存DATA和RESOURCE。對于本地圖檔,隻緩存RESOURCE。
DiskCacheStrategy.AUTOMATIC (預設政策):
它會嘗試對本地和遠端圖檔使用最佳的政策。當你加載遠端資料(比如,從URL下載下傳)時,AUTOMATIC 政策僅會存儲未被你的加載過程修改過(比如,變換,裁剪–譯者注)的原始資料(DATA),因為下載下傳遠端資料相比調整磁盤上已經存在的資料要昂貴得多。對于本地資料,AUTOMATIC 政策則會僅存儲變換過的縮略圖(RESOURCE),因為即使你需要再次生成另一個尺寸或類型的圖檔,取回原始資料也很容易。
DiskCacheStrategy.DATA:
表示隻緩存未被處理的檔案。我的了解就是我們獲得的stream。它是不會被展示出來的,需要經過裝載decode,對圖檔進行壓縮和轉換,等等操作,得到最終的圖檔才能被展示。
DiskCacheStrategy.NONE:
表示不緩存任何内容。
DiskCacheStrategy.RESOURCE:
表示隻緩存轉換過後的圖檔。(也就是經過decode,轉化裁剪的圖檔)
預設的政策為DiskCacheStrategy.AUTOMATIC,改變政策也很簡單, 比如
ImgurGlide.with(vh.imageView)
.load(image.link)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(vh.imageView);
我們這裡就用預設磁盤緩存政策分析
通過上一篇的加載流程,我們可以直接找到DecodeJob類,擷取磁盤緩存的步驟就在裡面
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 檢查磁盤緩存政策,是否解碼緩存的轉換圖檔
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
// 檢查磁盤緩存政策,是否解碼緩存的原始資料,這裡了解為流
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
...
}
這3個方法,非常繞,
30-58行,這段代碼的邏輯就是根據磁盤緩存政策把檢查步驟往下推,第一步檢查轉換過的圖檔是否有緩存,
第二步檢查未被轉化過的圖檔資源緩存,第三步檢查資料源,比如本地相冊原始圖檔,網絡URL原始圖檔。
如果目标圖檔已經加載過緩存在磁盤了,但是記憶體緩存中還沒有,那麼其實在第一步檢查中就會擷取到,然後加載顯示。
這也是大多數的情況加載磁盤緩存過的圖檔資源,這裡以這種情況為例繼續分析
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object> {
...
@Override
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
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);
currentKey =
new ResourceCacheKey(
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
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;
}
...
}
直接跟蹤類ResourceCacheGenerator
25-34,根據圖檔URL生成一個磁盤緩存ResourceCacheKey
第35行,根據這個key擷取磁盤緩存圖檔File
第38行,根據這個File擷取初始化Glide時注冊的ModelLoaders,猜也猜得到大概是哪幾個了,必定是其中一個
.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
.append(File.class, InputStream.class, new FileLoader.StreamFactory())
.append(File.class, File.class, new FileDecoder())
.append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
// Compilation with Gradle requires the type to be specified for UnitModelLoader here.
.append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())
這裡公布下答案,ByteBufferFileLoader,
接着第52行,根據這個ByteBufferFileLoader去加載磁盤緩存圖檔檔案然後一層一層回調回去顯示
其實到這裡Glide磁盤緩存就差不多了,接下來繼續分析緩存圖檔在哪存入和擷取的呢
第35行,這裡就是根據ResourceCacheKey擷取磁盤緩存圖檔File,
在哪存儲的呢?
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {
...
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
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;
}
...
}
第10行,當從網絡下載下傳後會執行到這裡,這裡就是存儲到磁盤的起點
跟蹤進入,會發現擷取磁盤檔案和存儲都會調用helper.getDiskCache(),接着就先弄清楚它
final class DecodeHelper<Transcode> {
...
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
...
}
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
interface DiskCacheProvider {
DiskCache getDiskCache();
}
...
}
這裡首先要知道diskCacheProvider是哪個類的執行個體,從上面的代碼可知是DiskcacheProvider接口的一個實作類,
通過全局搜尋可以知道隻有一個實作類LazyDiskCacheProvider,那麼diskCacheProvider肯定就是
LazyDiskCacheProvider的執行個體了
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
@Override
...
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
...
}
第12行,可以知道從外面傳了一個factory建立了一個DiskCache的執行個體,DiskCache是一個接口,全局搜尋可以知道DiskCache
有2個實作類DiskLruCacheWrapper、DiskCacheAdapter,從上面的代碼分析肯定不是DiskCacheAdapter的執行個體,如果
是就不會有15行的建立DiskCacheAdapter執行個體,是以,可以推斷第12行建立的是DiskLruCacheWrapper執行個體,這裡可以透露下
其實磁盤緩存的讀寫都是這個執行個體裡面實作的,不過先不看這個類,我們先要知道這個factory從哪傳入的
public final class GlideBuilder {
...
public Glide build(Context context) {
// 開始初始化glide
...
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
...
}
...
}
還記得初始Glide執行個體那裡嗎,第8行,可以看到預設情況下初始化磁盤存儲,然後InternalCacheDiskCacheFactory執行個體一級一級
往下傳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);
}
}
第4-5 行,這裡的2個參數預設是磁盤内部存儲緩存目錄、磁盤緩存大小為250M,構造方法最後調用了super構造方法
16-25行,重寫了getCacheDirectory(),這個方法的作用就是建立磁盤内部緩存目錄,接着跟蹤super
public class DiskLruCacheFactory implements DiskCache.Factory {
...
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);
}
...
}
第10-21行,有個build()方法,用于建立DiskLruCacheWrapper執行個體,與之前的呼應了吧,跟蹤第21行,
可以知道建立DiskLruCacheWrapper執行個體
public class DiskLruCacheWrapper implements DiskCache {
...
public static DiskCache create(File directory, long maxSize) {
return new DiskLruCacheWrapper(directory, maxSize);
}
...
}
到這裡應該已經清楚了當存儲或者擷取磁盤緩存圖檔資源時用到的DiskCache就是DiskLruCacheWrapper執行個體,
接下來分析DiskLruCacheWrapper類
public class DiskLruCacheWrapper implements DiskCache {
...
protected DiskLruCacheWrapper(File directory, long maxSize) {
this.directory = directory;
this.maxSize = maxSize;
this.safeKeyGenerator = new SafeKeyGenerator();
}
...
}
這是DiskLruCacheWrapper的構造方法,把緩存目錄和大小傳入作為成員變量
第6行,構造SafeKeyGenerator執行個體,跟蹤
public class SafeKeyGenerator {
...
private final LruCache<Key, String> loadIdToSafeHash = new LruCache<>(1000);
private final Pools.Pool<PoolableDigestContainer> digestPool =
FactoryPools.threadSafe(10,
new FactoryPools.Factory<PoolableDigestContainer>() {
@Override
public PoolableDigestContainer create() {
try {
return new PoolableDigestContainer(MessageDigest.getInstance("SHA-256"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
});
...
}
SafeKeyGenerator是一個使用LruCache算法儲存磁盤緩存圖檔加密名稱的一個工具,這裡就不深入分析它了,
接下來看DiskLruCacheWrapper的put()和get(),先跟蹤get()
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key);
}
File result = null;
try {
// It is possible that the there will be a put in between these two gets. If so that shouldn't
// be a problem because we will always put the same value at the same key so our input streams
// will still represent the same data.
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to get from disk cache", e);
}
}
return result;
}
...
}
第5行,加密key,比如,httpxxx 加密後為 32fgdg44r2xx,
第14行,擷取磁盤緩存圖檔檔案,接着傳回,這裡又是難點的開始了,跟蹤進去
public class DiskLruCacheWrapper implements DiskCache {
...
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
...
}
public final class DiskLruCache implements Closeable {
...
public static DiskLruCache open(File directory, int appVersion, int valueCount, long
maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
...
}
第5行,DiskLruCache.open()建構DiskLruCache執行個體
24-34行,建立日志檔案和備份,
第37行,建立DiskLruCache執行個體
第40行,如果日志檔案存在,開始讀取日志檔案内容,跟蹤
public final class DiskLruCache implements Closeable {
...
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
...
}
6-10行,從日志檔案中讀取介紹資訊,版本号啊神馬的
20-28行,循環讀取剩下的日志資訊,當讀取到末尾退出循環,讀取到的資訊放入lruEntries中
44-60行,讀取日志資訊,從讀取到的一行資訊截取空格間的字元串,也就是加密key
62-65行,lruEntries開始存Entry對象,Entry對象又包含上面讀取出來的加密key,lruEntries很多地方都會用到
第64行,跟蹤
public final class DiskLruCache implements Closeable {
...
private final class Entry {
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
cleanFiles = new File[valueCount];
dirtyFiles = new File[valueCount];
// The names are repetitive so re-use the same builder to avoid allocations.
StringBuilder fileBuilder = new StringBuilder(key).append('.');
int truncateTo = fileBuilder.length();
for (int i = 0; i < valueCount; i++) {
fileBuilder.append(i);
cleanFiles[i] = new File(directory, fileBuilder.toString());
fileBuilder.append(".tmp");
dirtyFiles[i] = new File(directory, fileBuilder.toString());
fileBuilder.setLength(truncateTo);
}
}
}
...
}
可以知道構造Entry實體的時候,根據讀取日志中的加密key,其實也就是磁盤緩存圖檔檔案名,生成file對象作為Entry實體
成員變量
接着看DiskLruCache的get()方法
public final class DiskLruCache implements Closeable {
...
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
...
}
第5行,從lruEntries中根據加密key擷取Entry對象,如果有,往下執行
14-19行,從Entry對象擷取緩存檔案,如果存在往下執行
第30行,如果緩存檔案存在建構一個Value實體對象,包含緩存檔案傳回
到這裡擷取磁盤緩存圖檔就差不多了,接下來看下DiskLruCacheWrapper.put(),存儲磁盤緩存圖檔檔案
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
public void put(Key key, Writer writer) {
// We want to make sure that puts block so that data is available when put completes. We may
// actually not write any data if we find that data is written by the time we acquire the lock.
// 傳回加密後的key,采用Lrucache算法
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
}
try {
// We assume we only need to put once, so if data was written while we were trying to get
// the lock, we can simply abort.
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
if (current != null) {
return;
}
DiskLruCache.Editor editor = diskCache.edit(safeKey);
if (editor == null) {
throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
}
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to put to disk cache", e);
}
}
} finally {
writeLocker.release(safeKey);
}
}
...
}
17-21行,跟get()差不多,先擷取DiskLruCache執行個體,然後get(key)擷取緩存檔案,如果存在,就傳回null,表示不需要存儲,
如果沒有就繼續執行
第23行,很重要,先跟蹤,一會再回到這裡
public final class DiskLruCache implements Closeable {
...
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Value is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.append(DIRTY);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
journalWriter.flush();
return editor;
}
...
}
13-23行,lruEntrie熟悉了吧,又來了哦,判斷存儲有加密key沒有,沒有就建構Entry實體執行個體把加密key存入,跟前面
get()邏輯一樣的
25-34行,根據加密key建構一串特定字元串,然後寫入磁盤日志檔案中,這個日志用于記錄緩存圖檔檔案名稱的,下次擷取
緩存檔案就從這個日志讀取名稱,前面get()部分分析過了
接着回到DiskLruCacheWrapper.put()
public class DiskLruCacheWrapper implements DiskCache {
...
@Override
public void put(Key key, Writer writer) {
// We want to make sure that puts block so that data is available when put completes. We may
// actually not write any data if we find that data is written by the time we acquire the lock.
// 傳回加密後的key,采用Lrucache算法
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
}
try {
// We assume we only need to put once, so if data was written while we were trying to get
// the lock, we can simply abort.
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
if (current != null) {
return;
}
DiskLruCache.Editor editor = diskCache.edit(safeKey);
if (editor == null) {
throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
}
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to put to disk cache", e);
}
}
} finally {
writeLocker.release(safeKey);
}
}
...
}
27-32行,根據之前傳回的DiskLruCache.Editor執行個體,擷取需要磁盤緩存的圖檔檔案名稱,
然後寫入磁盤好了,到這裡磁盤的存和取都分析了一遍,相信也比較清楚了,
最後幾句話總結下原理
根據傳入的URL和相關參數組成一個key,然後從記憶體緩存的弱引用中查找是否相應的圖檔資源,如果有
就傳回,沒有就從記憶體緩存的Lrucache緩存中查找,如果有就傳回并存入弱引用緩存,如果沒有就去檢視
磁盤緩存,磁盤緩存有幾種政策,常用的政策還是緩存轉換後的圖檔,先加密key,然後去磁盤日志檔案中
查找有沒有記錄,如果有就根據記錄的檔案名稱擷取圖檔檔案并傳回,如果沒有記錄,就從網絡擷取圖檔
資源流,根據加密key建立一份記錄寫入日志檔案中,并把圖檔資源流寫入磁盤緩存中
歡迎大家指教,其中還是有很多不是很明白,好了,到這裡又可以愉快的玩耍了
Android 圖檔加載架構Glide主流程源碼分析