因爲以前項目搭建的是 MVP 架構,由RxJava + Glide + OKHttp + Retrofit
等開源框架組合而成,以前也都是停留在使用層面上,沒有深刻的研究,最近打算把它們所有攻下,尚未關注的同窗能夠先關注一波,看完這個系列文章,(不論是面試仍是工做中處理問題)相信你都在知道原理的狀況下,處理問題更加駕輕就熟。java
Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析 Glide 執行流程面試
Android 圖片加載框架 Glide 4.9.0 (二) 從源碼的角度分析 Glide 緩存策略算法
從源碼的角度分析 Rxjava2 的基本執行流程、線程切換原理緩存
從源碼的角度分析 OKHttp3 (一) 同步、異步執行流程性能優化
從源碼的角度分析 Retrofit 網絡請求,包含 RxJava + Retrofit + OKhttp 網絡請求執行流程app
在上一篇中,咱們知道了 Glide 框架的最基本的執行流程,那麼只知道基本執行流程,這顯然是不夠的,咱們要深挖 Glide 框架的細節處理原理,好比緩存機制,圖片處理等,這一篇咱們就一塊兒去探索 Glide 的緩存機制。框架
Glide 緩存機制能夠說是設計的很是完美,考慮的很是周全,下面就以一張表格來講明下 Glide 緩存。異步
緩存類型 | 緩存表明 | 說明 |
---|---|---|
活動緩存 | ActiveResources | 若是當前對應的圖片資源是從內存緩存中獲取的,那麼會將這個圖片存儲到活動資源中。 |
內存緩存 | LruResourceCache | 圖片解析完成並最近被加載過,則放入內存中 |
磁盤緩存-資源類型 | DiskLruCacheWrapper | 被解碼後的圖片寫入磁盤文件中 |
磁盤緩存-原始數據 | DiskLruCacheWrapper | 網絡請求成功後將原始數據在磁盤中緩存 |
若是對 Glide 執行流程不明白的能夠先看 Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析一次最簡單的執行流程
在介紹緩存原理以前,先來看一張加載緩存執行順序,先有個印象。
不論是內存緩存仍是磁盤緩存,存儲的時候確定須要一個惟一 key 值,那麼 Glide cache key 是怎麼生成的?經過上一篇源碼加載流程介紹,咱們知道在 Engine 的 load 函數中進行對 key 的生成。下面咱們就經過代碼來看一下。
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
...
public synchronized <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, Executor callbackExecutor) {
....
//1. 生成緩存惟一 key 值,model 就是圖片地址
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
....
}
...
}
//生成 key
EngineKey buildKey(Object model, Key signature, int width, int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
複製代碼
class EngineKey implements Key {
...
@Override
public boolean equals(Object o) {
if (o instanceof EngineKey) {
EngineKey other = (EngineKey) o;
return model.equals(other.model)
&& signature.equals(other.signature)
&& height == other.height
&& width == other.width
&& transformations.equals(other.transformations)
&& resourceClass.equals(other.resourceClass)
&& transcodeClass.equals(other.transcodeClass)
&& options.equals(other.options);
}
return false;
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = model.hashCode();
hashCode = 31 * hashCode + signature.hashCode();
hashCode = 31 * hashCode + width;
hashCode = 31 * hashCode + height;
hashCode = 31 * hashCode + transformations.hashCode();
hashCode = 31 * hashCode + resourceClass.hashCode();
hashCode = 31 * hashCode + transcodeClass.hashCode();
hashCode = 31 * hashCode + options.hashCode();
}
return hashCode;
}
...
}
複製代碼
根據註釋和代碼能夠看到傳入的參數之多,主要是根據 url ,簽名,寬高等,其內部重寫了 hashCode,equals,來保證對象的惟一性。
經過下面代碼開啓內存緩存,固然 Glide 默認是爲咱們開啓了內存緩存因此不須要咱們調用 skipMemoryCache
//在 BaseRequestOptions 成員變量中默認爲內存緩存開啓。
private boolean isCacheable = true;
//調用層調用
Glide.
with(MainActivity.this.getApplication()).
//開啓使用內存緩存
skipMemoryCache(true).
into(imageView);
複製代碼
如今緩存 key 有了以後,就能夠根據 key 拿到對應的緩存了,經過文章開始的介紹,咱們知道先加載活動緩存,若是活動緩存沒有在加載內存緩存,先看下代碼;
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
...
public synchronized <R> LoadStatus load( ....//參數 ) {
....
//1. 生成緩存惟一 key 值,model 就是圖片地址
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//2. 優先加載內存中的活動緩存 - ActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//3. 若是活動緩存中沒有,就加載 LRU 內存緩存中的資源數據。
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
...
}
複製代碼
不知道你們經過上面代碼跟註釋(2,3 ) , 有沒有發現爲何 Glide 會弄 2 個內存緩存(一個 Map + 弱引用,一個 LRU 內存緩存),你們有沒有想過爲何?在看 郭霖大神對 Glide 緩存機制分析 中說道, ActiveResources 就是一個弱引用的 HashMap ,用來緩存正在使用中的圖片,使用 ActiveResources 來緩存正在使用中的圖片,能夠保護這些圖片不會被 LruCache 算法回收掉
,起初當我看見這句話的時候,我是真的很不理解,由於 Lru 是最近最少時候纔會回收尾端數據,那麼這裏的 ActiveResources 來緩存正在使用中的圖片,能夠保護這些圖片不會被 LruCache 算法回收掉
,我是越想越以爲矛盾,後來中午吃飯的時候,靈光一閃忽然想到了一種狀況,我也不知道是否是這樣,先看下面一張圖。
詳細舉例說明: 好比咱們 Lru 內存緩存 size 設置裝 99 張圖片,在滑動 RecycleView 的時候,若是剛剛滑動到 100 張,那麼就會回收掉咱們已經加載出來的第一張,這個時候若是返回滑動到第一張,會從新判斷是否有內存緩存,若是沒有就會從新開一個 Request 請求,很明顯這裏若是清理掉了第一張圖片並非咱們要的效果。因此在從內存緩存中拿到資源數據的時候就主動添加到活動資源中,而且清理掉內存緩存中的資源。這麼作很顯然好處是 保護不想被回收掉的圖片不被 LruCache 算法回收掉,充分利用了資源。
我也不知道這樣理解是否正確,但願若是有其它的含義,麻煩告知一下,謝謝 !
上面咱們說到了 Glide 爲何會設計 2 個內存緩存,下面咱們就對這 2 個緩存的 存/取/刪 來具體說明下。
獲取活動資源
經過以前的介紹咱們知道,活動緩存是在Engine load
中獲取
public synchronized <R> LoadStatus load( ....//參數 ) {
//1. 優先加載內存中的活動緩存 - ActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
// 若是找到獲取資源,就返回上層
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
...
}
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
// 經過 活動資源的 get 函數拿到活動資源的緩存
EngineResource<?> active = activeResources.get(key);
if (active != null) {
//正在使用,引用計數 +1
active.acquire();
}
return active;
}
複製代碼
繼續看 get 的具體實現
final class ActiveResources {
//
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
...
//外部調用 get 函數拿到活動資源緩存
@Nullable
synchronized EngineResource<?> get(Key key) {
//經過 HashMap + WeakReference 的存儲結構
//經過 HashMap 的 get 函數拿到活動緩存
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
//繼承 WeakReference 弱引用,避免內存泄漏。
@VisibleForTesting
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
....
}
複製代碼
經過上面代碼咱們知道活動緩存是 Map + WeakReference 來進行維護的,這樣作的好處是避免圖片資源內存泄漏。
存儲活動資源
經過文章開頭表格中提到,活動資源是加載內存資源以後存儲的,那麼咱們就來看下什麼時候加載內存資源,看下面代碼。
仍是在Engine load
函數中
//1. 若是活動緩存中沒有,就加載 LRU 內存緩存中的資源數據。
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 加載內存中圖片資源
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//2. 拿到內存緩存,內部將當前的 key 緩存 remove 了
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
// 3. 若是內存緩存不爲空,則引用計數器 +1
cached.acquire();
//4. 添加進活動緩存中
activeResources.activate(key, cached);
}
return cached;
}
複製代碼
經過註釋 4 ,咱們知道在拿到內存緩存的時候,先將內存緩存的當前 key 刪除了,而後添加到活動緩存中。
清理活動資源
經過上一篇 Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析一次最簡單的執行流程 中得知,咱們是在EngineJob
中發出的通知回調,告知Engine onResourceReleased
來刪除 活動資源。下面咱們在回顧一下執行流程,那麼咱們就從 EngineJob 發出通知開始看吧:
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
....
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
@Synthetic
void notifyCallbacksOfResult() {
.....
//回調上層 Engine 任務完成了
listener.onEngineJobComplete(this, localKey, localResource);
//遍歷資源回調給 ImageViewTarget ,並顯示
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
//這裏是通知發出上層刪除活動資源數據
decrementPendingCallbacks();
}
....
}
//重要的就是這裏了
@Synthetic
synchronized void decrementPendingCallbacks() {
if (decremented == 0) {
if (engineResource != null) {
//若是不爲空,那麼就調用內部 release 函數
engineResource.release();
}
}
}
複製代碼
看一下 EngineResource 的 release 函數
class EngineResource<Z> implements Resource<Z> {
...
void release() {
synchronized (listener) {
synchronized (this) {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
//這裏每次調用一次 release 內部引用計數法就會減一,到沒有引用也就是爲 0 的時候 ,就會通知上層
if (--acquired == 0) {
//回調出去,Engine 來接收
listener.onResourceReleased(key, this);
}
}
}
}
...
}
複製代碼
根據註釋,咱們知道這裏用了引用計數法,有點像 GC 回收的 引用計數法的影子。也就是說,當徹底沒有使用這樣圖片的時候,就會把活動資源清理掉,接着往下看,會調用 Engine 的 onResourceReleased 函數。
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
。。。
//接收來自 EngineResource 的調用回調
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//1. 收到當前圖片沒有引用,清理圖片資源
activeResources.deactivate(cacheKey);
//2. 若是開啓了內存緩存
if (resource.isCacheable()) {
//3. 將緩存存儲到內存緩存中。
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
。。。
}
複製代碼
經過上面註釋 1 能夠知道,這裏首先會將緩存圖片從 activeResources 中移除,而後再將它 put 到 LruResourceCache 內存緩存當中。這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用 LruCache 來進行緩存的功能,設計的真的很巧妙。
獲取內存資源
不知道有沒有小夥伴注意到,其實在講活動資源存儲的時候已經涉及到了內存緩存的存儲,下面咱們在來看一下,具體代碼以下:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
...//忽略一些成員變量跟構造函數
public synchronized <R> LoadStatus load( ...//忽略參數 ) {
....
//1. 加載內存緩存
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
//若是內存緩存中有,就通知上層,最後在 SingleRequest 接收
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
}
...
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//2. 經過 getEngineResourceFromCache 獲取內存資源
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {//若是內存資源存在
//則引用計數 +1
cached.acquire();
//3. 將內存資源存入活動資源
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
//經過 Engine load 中傳參可知,cache 就是 Lru 內存資源緩存
//2.1 這裏是經過 remove 刪除來拿到緩存資源
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
複製代碼
經過註釋1得知,這裏是開始加載內存緩存中的資源;
經過註釋2.1 可知,這裏經過 Lru 內存緩存的 remove 來拿到內存緩存
最後將內存存儲到活動緩存中,並緩存當前找到的內存緩存。
這裏的活動緩存跟內存緩存緊密聯繫在一塊兒,環環相扣,就像以前咱們的疑問,爲何 Glide 會設計 2 個內存緩存的緣由了。
存儲內存資源
經過 EngineResource 的引用計數法機制 release 函數,只要沒有引用那麼就回調,請看下面代碼:
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
....
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
@Synthetic
void notifyCallbacksOfResult() {
.....
//這裏是通知發出上層刪除活動資源數據
decrementPendingCallbacks();
}
....
}
//重要的就是這裏了
@Synthetic
synchronized void decrementPendingCallbacks() {
if (decremented == 0) {
if (engineResource != null) {
//若是不爲空,那麼就調用內部 release 函數
engineResource.release();
}
}
}
void release() {
synchronized (listener) {
synchronized (this) {
//引用計數爲 0 的時候,回調。
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
}
}
複製代碼
最後會回調到 Engine 的 onResourceReleased
函數:
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//1. 清理活動緩存
activeResources.deactivate(cacheKey);
//若是開啓了內存緩存
if (resource.isCacheable()) {
//2. 清理出來的活動資源,添加進內存緩存
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
複製代碼
到了這裏咱們知道,在清理活動緩存的時候添加進了內存緩存。
清理內存緩存
這裏的清理是在獲取內存緩存的時候經過 remove 清理掉的,詳細能夠看內存緩存獲取的方式。
經過上面分析可知,內存緩存有活動緩存和內存資源緩存,下面看一個圖來總結下它們相互之間是怎麼配合交換數據的。
總結下步驟:
在介紹磁盤緩存以前先看一張表格
緩存表示 | 說明 |
---|---|
DiskCacheStrategy.NONE | 表示不開啓磁盤緩存 |
DiskCacheStrategy.RESOURCE | 表示只緩存轉換以後的圖片。 |
DiskCacheStrategy.ALL | 表示既緩存原始圖片,也緩存轉換事後的圖片。 |
DiskCacheStrategy.DATA | 表示只緩存原始圖片 |
DiskCacheStrategy.AUTOMATIC | 根據數據源自動選擇磁盤緩存策略(默認選擇) |
上面這 4 中參數其實很好理解,這裏有一個概念須要記住,就是當咱們使用 Glide 去加載一張圖片的時候,Glide 默認並不會將原始圖片展現出來,而是會對圖片進行壓縮和轉換,總之就是通過種種一系列操做以後獲得的圖片,就叫轉換事後的圖片。而 Glide 默認狀況下在硬盤緩存的就是 DiskCacheStrategy.AUTOMATIC
如下面的代碼來開啓磁盤緩存:
Glide.
with(MainActivity.this.getApplication()).
//使用磁盤資源緩存功能
diskCacheStrategy(DiskCacheStrategy.RESOURCE).
into(imageView);
複製代碼
知道了怎麼開啓,下面咱們就來看一下磁盤緩存的的加載與存儲。
這 2 個加載流程幾乎如出一轍,只是加載的數據源不一樣,下面咱們具體來看一下
獲取資源數據
經過上一篇Glide 加載流程 咱們知道,若是在活動緩存、內存緩存中沒有找數據,那麼就從新開啓一個 GlideExecutor 線程池在 DecodeJob run 執行新的請求,下面咱們就直接來看 DecodeJob run 函數,跟着它去找 資源數據的加載:
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
...
@Override
public void run() {
...
try {
//若是取消就通知加載失敗
if (isCancelled) {
notifyFailed();
return;
}
//1. 執行runWrapped
runWrapped();
} catch (CallbackException e) {
...
}
}
...
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//2. 找到執行的狀態
stage = getNextStage(Stage.INITIALIZE);
//3. 找到具體執行器
currentGenerator = getNextGenerator();
//4. 開始執行
runGenerators();
break;
...
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE: //3.1解碼後的資源執行器
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE://原始數據執行器
return new DataCacheGenerator(decodeHelper, this);
case SOURCE://新的請求,http 執行器
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
複製代碼
經過上面分析的代碼跟註釋,咱們知道這裏是在找具體的執行器,找完了以後註釋 4 開始執行,如今咱們直接看註釋 4。先提一下 註釋 3.1 由於外部咱們配置的是 RESOURCE 磁盤資源緩存策略,因此直接找到的是 ResourceCacheGenerator
執行器。
private void runGenerators() {
//若是當前任務沒有取消,執行器不爲空,那麼就執行 currentGenerator.startNext() 函數
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
..
}
複製代碼
經過上面代碼可知,主要是執行 currentGenerator.startNext()
就句代碼,currentGerator 是一個接口,經過註釋 3.1 咱們知道這裏它的實現類是 ResourceCacheGenerator
,那麼咱們具體看下 ResourceCacheGenerator
的 startNext 函數;
class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {
...
@Override
public boolean startNext() {
...
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
...
//1. 拿到資源緩存 key
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//2. 經過 key 獲取到資源緩存
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3. 獲取一個數據加載器
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//3.1 爲資源緩存文件,構建一個加載器,這是構建出來的是 ByteBufferFileLoader 的內部類 ByteBufferFetcher
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
//3.2 利用 ByteBufferFetcher 加載,最後把結果會經過回調給 DecodeJob 的 onDataFetcherReady 函數
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
...
}
複製代碼
經過上面註釋能夠獲得幾點信息
存儲資源數據
先來看下面一段代碼:
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
...
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
....
stage = Stage.ENCODE;
try {
//1. 是否能夠將轉換後的圖片緩存
if (deferredEncodeManager.hasResourceToEncode()) {
//1.1 緩存入口
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
...
}
onEncodeComplete();
}
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
//1.2 將 Bitmap 緩存到資源磁盤
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
複製代碼
經過上面咱們知道 http 請求到圖片輸入流以後通過一系列處理,轉換獲得目標 Bitmap 資源,最後經過回調到 DecodeJob 進行緩存起來。
清理資源緩存
獲取原始數據
參考上小節DiskCacheStrategy.RESOURCE
獲取資源,不一樣的是把 ResourceCacheGenerator 換成 DataCacheGenerator 加載了。
存儲原始數據
這裏既然存的是原始數據那麼咱們直接從 http 請求以後的響應數據開始查看,經過上一篇咱們知道是在 HttpUrlFetcher 中請求網絡,直接定位到目的地:
public class HttpUrlFetcher implements DataFetcher<InputStream> {
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//1. 經過 loadDataWithRedirects 來進行http 請求,返回 InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//2. 將請求以後的數據返回出去
callback.onDataReady(result);
} catch (IOException e) {
...
} finally {
...
}
}
}
複製代碼
根據註釋能夠得知,這裏主要用於網絡請求,請求響應數據回調給 MultiModelLoader 中。咱們看下 它具體實現:
class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {
...
@Override
public void onDataReady(@Nullable Data data) {
//若是數據不爲空,那麼就回調給 SourceGenerator
if (data != null) {
callback.onDataReady(data);
} else {
startNextOrFail();
}
}
....
}
複製代碼
這裏的 callback 指的是 SourceGenerator ,繼續跟
class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {
....
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//1. 收到網絡下載好的圖片原始數據,賦值給成員變量 dataToCache
dataToCache = data;
//2. 交給 EngineJob
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
....
}
複製代碼
經過上面註釋能夠知道 cb.reschedule(); 最後回調到 EngineJob 類,會執行 reschedule(DecodeJob<?> job) 函數的 getActiveSourceExecutor().execute(job); 用線程池執行任務,最後又回到了 DecodeJob 的 run 函數 拿到執行器DataCacheGenerator
,最終會在 SourceGenerator 的 startNext() 函數,以前流程代碼我就不貼了,上面講了不少次了,相信你們應該記得了,咱們直接看 startNext() 函數吧:
class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {
/**這個臨時的變量就是 http 請求回來的圖片原始數據 */
private Object dataToCache;
@Override
public boolean startNext() {
....
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//放入緩存
cacheData(data);
}
...
}
return started;
}
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());
//存儲原始數據
//經過 StreamEncoder encode 寫入文件
helper.getDiskCache().put(originalKey, writer);
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
複製代碼
經過上面代碼得知,這裏將原始數據寫入文件中了。
清理資源緩存
存儲
獲取
這裏咱們看一張流程圖吧
Glide 中複用池也起了一個很大的做用,這裏我就不貼代碼了,由於這個很好理解,你們能夠去 Glide 中的 Downsample 詳細瞭解。在這裏我就簡單說一下 Glide 中複用池的處理。
在 Glide 中,在每次解析一張圖片爲 Bitmap 的時候不論是內存緩存仍是磁盤緩存,都會從其BitmapPool 中查找一個可被複用的 Bitmap ,以後在將此塊的內存緩存起來。
注意:在使用複用池的時候,若是存在能被複用的圖片會重複使用該圖片的內存。 因此複用並不能減小程序正在使用的內存大小。Bitmap 複用,解決的是減小頻繁申請內存帶來的性能(抖動、碎片)問題。
能夠看到 Glide 在性能優化方面可謂是達到了極致,不光設計了多級複雜的緩存策略,就連開銷較大的 Bitmap 內存也利用了複用池進行了管理,因此就算用戶在沒有開啓全部緩存的狀況下, Bitmap 也保證了內存的合理使用,避免 OOM。儘量的減小了對象的建立開銷,保證了 Glide 加載的流暢性。
到這裏 Glide 4.9.0 版本的緩存機制也已經講完了,相信在看完以後,你將對 Glide 緩存機制有了必定了解。
感謝你的閱讀,文章中若有誤,還請指出,謝謝!