Glide4源碼解析系列
[Glide4源碼解析系列]–1.Glide初始化
[Glide4源碼解析系列]–2.Glide數據模型轉換與數據抓取
一、簡介
上一篇文章,我們梳理了一遍Glide的初始化流程,看到了Gilde在Glide#with一句簡單的代碼背後做了巨大的準備工作,而這所有的準備工作,都是爲了接下來順利地完成數據解析和顯示做了鋪墊。
在上一遍文章中提到,Glide的數據解碼流程可以分爲以下幾個步驟(如果沒看上一篇文章,建議可以先看看):
model(數據源)–>data(轉換數據)–>decode(解碼)–>transformed(縮放)–>transcoded(轉碼)–>encoded(編碼保存到本地)
那麼本篇文章就重點來看看Glide的數據轉換與數據抓取流程。
我們依然從一句代碼開始,那就是RequestManager#load
二、Glide資源加載前的準備
1. RequestManager.load裝載原始數據
通過上一篇文章,我們知道,Glide.with()最後給我們返回了一個請求管理工具,那就是RequestManger。通常如果是簡單的加載一張圖片的話,我們調用鏈如下:
Glide.with(this).load(url).into(iv_img);
在RequestManger的中,對load方法進行了多個類型的重載,基本上可以滿足日常圖片加載類型,如String, URL,Bitmap,InputStream,File等等。
爲了方便分析和流程的梳理,我們需要指定一個數據類型來進行跟蹤,其它的流程基本是一致的,只是過程中使用的轉換模型和解碼方式不一樣而已。
那麼,這裏就使用日常最常使用的,加載一個網絡圖片作爲分析源頭。我們直接進入:
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
首先看到,在最後這個as方法中,創建了一個RequestBuilder,即一個請求的構建者,用來構建個Request請求工具。
這裏需要注意一個參數,即as(Drawable.class),這個Drawable.class類型,將是最後轉碼得到的,最終用於顯示的數據類型。
其次,asDrawable()得到一個RequestBuilder,然後通過其load方法,將原始數據設置給RequstBuilder。
那麼,我們就來看看RequestBuild做了什麼。
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
可以看到,這裏並沒有馬上進入數據請求加載過程,而是簡單的將數據模式進行了保存,並將isModelSet設置爲true,然後返回。
那麼什麼時候纔開始進入數據加載流程。那就要來看看RequestBuilder#into()方法了。
2. RequestBuilder.into()啓動資源加載
1)生成請求參數,及顯示目標
public ViewTarget<ImageView, TranscodeType> into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
RequestOptions requestOptions = this.requestOptions;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
根據ImageView設置的縮放類型,配置一個請求參數,這裏設置的縮放工具,就是加載流程中transformed(縮放)使用到的工具。
重點看最後一個調用,第一個參數
glideContext.buildImageViewTarget(view, transcodeClass),
這裏會根據transcodeClass類型生成一個ViewTarget,這裏transcodeClass爲Drawable.class,所有將會生成一個DrawableImageViewTarget。
2)構建請求,並啓動數據加載
進入into方法中,過程比較簡單,直接看代碼中的註釋:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
//第一:通過isModelSet檢查是否通過load設置了數據源,否則拋出異常;
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
//第二:創建請求;
Request request = buildRequest(target, targetListener, options);
//第三:判斷當前請求是否已經存在,
//是的話,直接啓動請求;
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)) {
request.recycle();
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and untracking Targets, and obtaining View dimensions that
// are done in the individual Request.
previous.begin();
}
return target;
}
//第四:保存當前請求到ViewTarget的Tag中,
//並將Request添加RequestManager中進行跟蹤維護。
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
一看便知,重點在第二和第四兩步上。
首先來看下第二步,構建一個Request:
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions) {
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions);
}
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
//構建目標請求
Request mainRequest =
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions);
if (errorRequestCoordinator == null) {
return mainRequest;
}
int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth();
int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !errorBuilder.requestOptions.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}
Request errorRequest = errorBuilder.buildRequestRecursive(
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.requestOptions.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder.requestOptions);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
直接看第二個方法buildRequestRecursive,遞歸構建請求。從方法命名來看,請求不一定只有一個,而是會視情況遞歸地去構建多個請求,這些請求類型包括:
- 錯誤圖片請求(正常的請求出錯時,如果有配置該請求,則啓動該請求)
- 縮略圖請求(小圖請求,可以較快顯示。如有配置,在請求開始時,就會啓動)
- 目標圖片請求(目標圖片請求,在請求開始時,就是啓動)
如果除了目標圖片外,用戶還配置了錯誤圖片顯示,或縮略圖顯示,那麼,這時候會創建一個請求協調器,來協調各類型圖片間的請求順序。
在看協調器之前,我們先來看下Request這類,它是一個接口類,規定了請求相關的接口,如開始/停止/清除/回收請求……
public interface Request {
void begin();
void pause();
void clear();
boolean isPaused();
boolean isRunning();
boolean isComplete();
boolean isResourceSet();
boolean isCancelled();
boolean isFailed();
void recycle();
boolean isEquivalentTo(Request other);
}
而協調器,其實也是一個Request的實現類,比如上面第二個方法中的ErrorRequestCoordinator
public final class ErrorRequestCoordinator implements RequestCoordinator,
Request {
@Nullable
private final RequestCoordinator parent;
private Request primary;
private Request error;
public ErrorRequestCoordinator(@Nullable RequestCoordinator parent) {
this.parent = parent;
}
public void setRequests(Request primary, Request error) {
this.primary = primary;
this.error = error;
}
@Override
public void begin() {
if (!primary.isRunning()) {
primary.begin();
}
}
@Override
public void pause() {
if (!primary.isFailed()) {
primary.pause();
}
if (error.isRunning()) {
error.pause();
}
}
@Override
public void clear() {
primary.clear();
if (primary.isFailed()) {
error.clear();
}
}
@Override
public void onRequestFailed(Request request) {
if (!request.equals(error)) {
if (!error.isRunning()) {
error.begin();
}
return;
}
if (parent != null) {
parent.onRequestFailed(this);
}
}
//省略其餘方法......
}
- 在構建協調器後,會將目標圖片請求和錯誤圖片請求設置給協調器。
- 一旦請求begin,就會啓動目標圖片請求。
- 當目標圖片請求失敗時,就會啓動錯誤圖片請求。
其它的協調器也是類似的,只不過各類型請求啓動的時機不一樣罷了!
接着,我們回到剛剛buildRequestRecursive方法中,爲了方便分析,我們簡化一下流程,只看請求只有目標圖片的情況。
構建目標圖片用是在buildThumbnailRequestRecursive方法中:
private Request buildThumbnailRequestRecursive(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
if (thumbnailBuilder != null) {
//構建請求......
} else if (thumbSizeMultiplier != null) {
//構建請求......
} else {
// Base case: no thumbnail.
return obtainRequest(
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
}
}
看最後一個else,只有目標圖片的情況,這裏會構建並返回一個Request:
private Request obtainRequest(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions,
RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight) {
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListener,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory());
}
通過SingleRequest.obtain獲取到了一個SingleRequst的單例(這一堆的參數讓人忍不住想吐槽一下)。
到這構建好了Request,那麼接下來就是啓動請求了。
3)啓動請求
回到into方法中:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions options) {
//省略部分代碼......
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
在平時使用Glide過程中,我們可能會調用ImageView的setTag來緩存一些數據,但是在使用Glide加載圖片的時候,就會拋出異常,告訴我們使用Glide來加載圖片的ImageView不能調用setTag方法,這是爲什麼呢?原因就在這句代碼:
target.setRequest(request);
這句代碼會將Request緩存到ImageView的tag中,如果你確實需要緩存數據,那麼你只能調用setTag(int key, Object tag)給tag設置一個key。
最後,將這個請求放到RequestManager的請求隊列中,同時發起加載請求。
//RequestManager.java
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
//RequestTracker.java
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
那麼,Glide就通過RequestManager、RequestOption、Request,構建了一個請求序列,並通過監聽生命週期來動態管理Request的開啓、暫停、恢復、銷燬等。
三、開啓資源加載任務
來到SingleRequest#begin方法中(非完整代碼,省略一些不太緊要的代碼)
public void begin() {
//省略部分代碼...
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
//重點在以下if中
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
}
public void onSizeReady(int width, int height) {
//省略部分代碼...
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this);
if (status != Status.RUNNING) {
loadStatus = null;
}
}
在begin方法中,如果圖片顯示尺寸有效,會直接調用onSizeReady。否則, 會調用target.getSize,去計算圖片尺寸,計算完畢後,同樣會回調onSizeReady方法。
因此,最終都會進入到onSizeReady方法中,進而調用Engine(請求加載引擎)的load方法。
代碼中簡單標示了加載流程。
//Engine.java
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();
//1:創建資源索引key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//2:從內存中當前正在顯示的資源緩存加載圖片
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;
}
//3:從內存緩存資源中加載圖片
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;
}
//4:獲取已經存在的加載任務
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);
}
//5:新建加載任務,用於啓動解碼任務
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//6:新建解碼任務,真正執行數據加載和解碼的類
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
//7:緩存加載任務
jobs.put(key, engineJob);
engineJob.addCallback(cb);
//8:開啓解碼任務
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
以上共有8個步驟來獲取或開始一個資源的加載和解碼。分析一下:
1:創建資源索引key。
我們可以看到生成一個索引key需要資源本身、圖片寬高、轉換類型、加載參數等等,只要這些都一致的情況下,才判定爲一個相同的圖片資源加載。所以,即便是要顯示的ImageView寬高不一樣,Glide都會重新執行一次加載過程,而不是內存中加載已有的圖片資源。
2和3:
如果要加載的圖片已經正在顯示,直接使用已有的資源。如果圖片沒有在顯示,但是已經正好還在內存緩存中,沒有被銷燬,那麼直接使用緩存中的資源
4到8:
如果內存中並沒有可以直接使用的圖片資源,那麼就要開始從網絡或者本地硬盤中去加載一張圖片。
還記得上一篇文中說到的,初始化過程中會創建幾個不同類的線程池,用於加載圖片資源嗎?Glide將每一個請求都封裝爲一個解碼任務DecodeJob,並扔到線程池中,以此來開啓任務的異步加載。
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
如此就很明瞭了,DecodeJob肯定是一個繼承Runnable的類,任務啓動的入口就在run方法中。
//DecodeJob.java
public void run() {
//省略部分代碼...
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
//重點調用
runWrapped();
} catch (Throwable t) {
//省略日誌打印和註釋...
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
} finally {
// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
// close in all cases anyway.
if (localFetcher != null) {
localFetcher.cleanup();
}
TraceCompat.endSection();
}
}
```
如果一切正常,那麼會進入runWrapped方法中
```java
//DecodeJob.java
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 Stage getNextStage(Stage current) {
switch (current) {
//初始狀態:下一狀態爲從處理過的資源緩存加載圖片
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
//狀態2:下一狀態從未處理過的原始資源加載圖片
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
//狀態3:下一狀態從遠程加載圖片
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
//狀態4:結束解碼
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
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 void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
<div class="se-preview-section-delimiter"></div>
==這幾個方法構成了Glide的解碼流程:==
- 嘗試從處理過的本地資源加載圖片
- 嘗試從未處理過的原始本地資源加載圖片
- 嘗試從遠程加載圖片
因此,這是一個嵌套的循環,通過狀態的切換來尋找下一個加載器,直到加載一張圖片,返回成功;或者找不到要加載的圖片,返回失敗。
==以上三個步驟分別對應以下三個加載器:==
- ResourceCacheGenerator
- DataCacheGenerator
- SourceGenerator
接下來終於要進入本文的重點部分了(鋪墊了一堆?,想講明白Glide真不容易啊~)
四、Glide數據模型轉換
1. 加載核心簡介
以上三個加載器是順序遍歷的,本文以加載一張網絡圖片來講解這個解碼過程,爲了便於理解與理清加載邏輯,我們不按照這個流程來,而是從最後一個SourceGenerator加載器入手,因爲,當你第一次加載一張新的網絡圖片時,本地並沒有這張網絡圖片的緩存。
從runGenerators方法中看到,Generator的入口是startNext()方法
//SourceGenerator.java
public boolean startNext() {
//1:判斷是否有緩存,如有,直接加載緩存
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
//2:沒有緩存,從遠程加載
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3:獲取數據加載器
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;
}
<div class="se-preview-section-delimiter"></div>
分爲兩種情況,第一中情況我們先不管,後面再詳細講到。直接來看第二種情況,也是首次加載的情況。
又是一個循環遍歷,遍歷所有的ModelLoader模塊加載器。爲了更好的理解,我們先來了解下Glide用來數據加載和解碼的幾個模塊。
類名 | 作用 |
---|---|
SourceGenerator繼承DataFetcherGenerator | DataFecher生成器 |
ModelLoader | 數據轉換和創建LoadData |
LoadData | 數據加載(包含DataFecher) |
DataFecher | 數據抓取 |
LoadPath | 加載器包含多個DecodePath |
DecodePath | 解碼器 |
從命名上也可以看出他們之間的些許聯繫:
- Generator是一個DataFecher生成器(還有ResourceCacheGenerator和DataCacheGenerator)
DataFether是一個數據抓取器,存放在LoadData中。
而這些LoadData是從哪裏生產的呢?就是ModelLoader。以上1-4構成了Glide數據轉換與獲取(如:String –> url –> InputStream)的核心;
5-6則構成Glide數據解碼的核心(5-6我們在下一篇文章再詳細分析)。當抓取到數據以後,需要對數據進行解碼,這時候就會用到DecodePath來進行解碼,而DecodePath正是存放在LoadPath當中的。
這幾個工具,基本構成了Glide數據加載過程的核心。
2. 模型轉換匹配
1)數據轉換,獲取ModelLoader
Glide是如何後獲取到匹配的模型加載器的?看startNext的第3步,進入helper.getLoadData(),其中helper爲DecoderHelper,解碼任務的幫助類。
//DecoderHelper.java
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders =
glideContext.getRegistry().getModelLoaders(model);
int size = modelLoaders.size();
for (int i = 0; i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
<div class="se-preview-section-delimiter"></div>
看到熟悉的代碼了嗎?
List<ModelLoader<Object, ?>> modelLoaders =
glideContext.getRegistry().getModelLoaders(model);
<div class="se-preview-section-delimiter"></div>
這裏獲取的不就是Glide在初始化的時候那個註冊器嗎?而getModelLoaders獲取的就是model類型對應的數據轉換器ModelLoader(如果不瞭解,建議先看上篇文章)。
下面只看關鍵的方法調用,
首先是ModelLoaderRegistry.java
//ModelLoaderRegistry.java
public synchronized <A> List<ModelLoader<A, ?>> getModelLoaders(A model) {
//獲取model類型對應的數據模型加載器
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
int size = modelLoaders.size();
List<ModelLoader<A, ?>> filteredLoaders = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ModelLoader<A, ?> loader = modelLoaders.get(i);
if (loader.handles(model)) {
filteredLoaders.add(loader);
}
}
return filteredLoaders;
}
private <A> List<ModelLoader<A, ?>> getModelLoadersForClass(Class<A> modelClass) {
List<ModelLoader<A, ?>> loaders = cache.get(modelClass);
if (loaders == null) {
//調用工廠方法,構建ModelLoader
loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));
cache.put(modelClass, loaders);
}
return loaders;
}
<div class="se-preview-section-delimiter"></div>
相信大家都看得懂,關鍵代碼就是multiModelLoaderFactory.build(modelClass)
//MultiModelLoaderFactory.java
synchronized <Model> List<ModelLoader<Model, ?>> build(Class<Model> modelClass) {
try {
List<ModelLoader<Model, ?>> loaders = new ArrayList<>();
//1:遍歷所有註冊的模型轉換器
for (Entry<?, ?> entry : entries) {
//2:過濾重複
if (alreadyUsedEntries.contains(entry)) {
continue;
}
//3:如果是要轉換的數據類型
if (entry.handles(modelClass)) {
alreadyUsedEntries.add(entry);
//4:添加構建好的ModelLoader
loaders.add(this.<Model, Object>build(entry));
alreadyUsedEntries.remove(entry);
}
}
return loaders;
} catch (Throwable t) {
alreadyUsedEntries.clear();
throw t;
}
}
private <Model, Data> ModelLoader<Model, Data> build(Entry<?, ?> entry) {
//調用工廠方法構建ModelLoader
return (ModelLoader<Model, Data>) Preconditions.checkNotNull(entry.factory.build(this));
}
//判斷數據類型是否爲要查找的數據類型,或者父類
//即this.modelClass == modelClass 或者 modelClass爲this.modelClass的子類
public boolean handles(Class<?> modelClass) {
return this.modelClass.isAssignableFrom(modelClass);
}
<div class="se-preview-section-delimiter"></div>
那麼,先把String對應的類型轉換以及相應的工廠列出來
Model | 轉換類型 | ModelLoader工廠 |
---|---|---|
String.class | InputStream.class | DataUrlLoader.StreamFactory |
String.class | InputStream.class | StringLoader.StreamFactory |
String.class | InputStream.class | StringLoader.FileDescriptorFactory() |
是不是覺得很簡單,就是三種轉換而已,事實並非如此,我們繼續往下看代碼。
//DataUrlLoader.java
private static final String DATA_SCHEME_IMAGE = "data:image";
public boolean handles(String url) {
return url.startsWith(DATA_SCHEME_IMAGE);
}
//DataUrlLoader#StreamFactory.java
public final ModelLoader<String, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new DataUrlLoader<>(opener);
}
<div class="se-preview-section-delimiter"></div>
從handles方法可以看出,DataUrlLoader是用來加載base64 Url圖片的。所以這會被過濾掉,不會用來處理普通的String類型圖片路徑。
剩下的兩個:
//StringLoader.java
public boolean handles(String model) {
return true;
}
//StringLoader#StreamFactory.java
public ModelLoader<String, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
//StringLoader#FileDescriptorFactory.java
public ModelLoader<String, ParcelFileDescriptor> build(MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, ParcelFileDescriptor.class));
}
<div class="se-preview-section-delimiter"></div>
你會發現,後面這兩個工廠什麼鬼?是不是走錯片場了?通過multiFactory.build的方法又回到MultiModelLoaderFactory中了?
但是,如果你夠仔細的話,你又會發現,此build非比build,這兒的build多了一個參數!!!
這就是Gilde數據模型轉換非常高明的地方了。這裏不僅將String.class類型的數據轉換成了Uri.class的數據,並且還精確縮小了搜索範圍,即要輸入爲Uri,又要輸出只爲InputStream.class和ParcelFileDescriptor.class的ModelLoader。
同時,也利用了Uri數據類型的ModelLoader的解析數據能力,來解析String類型的網絡圖片,不得不讚嘆Glide強大的架構設計思維。
通過這種數據類型轉換的能力,Glide幾乎可以無縫的加載任意類型的圖片數據。
好了,我們繼續往下:
public synchronized <Model, Data> ModelLoader<Model, Data> build(Class<Model> modelClass,
Class<Data> dataClass) {
try {
List<ModelLoader<Model, Data>> loaders = new ArrayList<>();
boolean ignoredAnyEntries = false;
//1:再次重新遍歷,只不過,這次model變成了Uri.class
for (Entry<?, ?> entry : entries) {
if (alreadyUsedEntries.contains(entry)) {
ignoredAnyEntries = true;
continue;
}
//2:同時滿足modelClass和dataClass纔是要查找的ModelLoader
if (entry.handles(modelClass, dataClass)) {
alreadyUsedEntries.add(entry);
loaders.add(this.<Model, Data>build(entry));
alreadyUsedEntries.remove(entry);
}
}
//3:如果多於1個loader,創建個兼容的多Model加載器MultiModelLoader
if (loaders.size() > 1) {
return factory.build(loaders, throwableListPool);
} else if (loaders.size() == 1) {
//4:只有1個,直接返回
return loaders.get(0);
} else {
if (ignoredAnyEntries) {
return emptyModelLoader();
} else {
throw new NoModelLoaderAvailableException(modelClass, dataClass);
}
}
} catch (Throwable t) {
alreadyUsedEntries.clear();
throw t;
}
}
<div class="se-preview-section-delimiter"></div>
遍歷邏輯基本於上一個build相同,只不過,最後返回的時候,如果遍歷到多個ModelLoader會創建一個MultiModelLoader,用來保存多個ModelLoader,其實也是一個協調器,類似新建Request是的多個類型Request時,用一個協調器來包裹和協調。
我們依舊把model類型爲Uri,data轉換類型爲InputStream和ParcelFileDescriptor的ModelLoader用表格列出來:
model | data | Factory |
---|---|---|
Uri.class | InputStream.class | HttpUriLoader.Factory |
Uri.class | InputStream.class | UriLoader.StreamFactory |
Uri.class | InputStream.class | AssetUriLoader.StreamFactory |
Uri.class | InputStream.class | MediaStoreImageThumbLoader.Factory |
Uri.class | InputStream.class | MediaStoreVideoThumbLoader.Factory |
Uri.class | ParcelFileDescriptor.class | AssetUriLoader.FileDescriptorFactory |
這裏列出了大體的ModelLoader,根據參數不一樣其工廠也會有些差別。
經過這麼一轉換,分化出來的ModelLoader就有七八個。但是每個loader都有其對應可以加載數據類型,又或者在構建LoadData的時候會有所判斷,來真正確認是否可以加載目標類型數據。
當然,你可能會想,這麼多的loader是否是又會分化出更多的ModelLoader出來?那麼我要告訴你的是,bingo~,答對了!
但是不用擔心,萬變不離其宗,所有的分化,到最後都會有個實際幹活的Loader。
這裏我們加載的是一張網絡圖片,可想而知,最篩選出來的只有可以加載網絡數據的ModelLoader,判別方法也很簡單,通過handlers方法就可以判別出來,具體就不再展開了。這裏能加載網絡圖片的有HttpUriLoader和UriLoader
以HttpUriLoader爲例:
//HttpUriLoader.java
private static final Set<String> SCHEMES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));
@Override
public boolean handles(Uri model) {
return SCHEMES.contains(model.getScheme());
}
//HttpUriLoader#Factory.java
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpUriLoader(multiFactory.build(GlideUrl.class, InputStream.class));
}
<div class="se-preview-section-delimiter"></div>
看到沒,又繼續轉換了,這回變成了GlideUrl和InputStream,但是這次不一樣了,因爲這兩個構成的Modeloader只有一個,並且還是實際幹活的!
model | data | Factory |
---|---|---|
GlideUrl.class | InputStream.class | HttpGlideUrlLoader.Factory |
//HttpGlideUrlLoader#Factory.java
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpGlideUrlLoader(modelCache);
}
<div class="se-preview-section-delimiter"></div>
經過這一些列遍歷和轉換構建,最終,Glide將得到一個ModelLoader列表,這個列表可能包含ModelLoader或者MultiModelLoader,取決於要加載的Model數據在註冊表中註冊的ModelLoad,以及ModelLoader相互間可發生的轉換。
2)構建LoadData
讓我們回到DecodeHelper:
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
int size = modelLoaders.size();
for (int i = 0; i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
//構建LoadData
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
<div class="se-preview-section-delimiter"></div>
得到ModelLoader後,將利用這些ModelLoader逐個構建LoadData
首先,看MultiModelLoader是如何構建LoadData的
//MultiModelLoader.java
public LoadData<Data> buildLoadData(Model model, int width, int height,
Options options) {
Key sourceKey = null;
int size = modelLoaders.size();
List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
if (modelLoader.handles(model)) {
LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
if (loadData != null) {
sourceKey = loadData.sourceKey;
fetchers.add(loadData.fetcher);
}
}
}
return !fetchers.isEmpty()
? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool)) : null;
}
<div class="se-preview-section-delimiter"></div>
MultiModelLoader會遍歷保存的ModelLoader列表,逐個構建LoadData,並將各個LoadData中的DataFetcher取出,存放在MultiFetcher中,從而MultiFetcher又成爲一個協調器。
再來看單個ModelLoader構建LoadData,同樣的,以Model爲String爲例
//StringLoader.java
public LoadData<Data> buildLoadData(String model, int width, int height,
Options options) {
//將String轉換爲Uri
Uri uri = parseUri(model);
return uri == null ? null : uriLoader.buildLoadData(uri, width, height, options);
}
<div class="se-preview-section-delimiter"></div>
還記得上面StringLoader構建的時候,進行Uri轉換嗎?此處的uriLoader正是轉換後的ModelLoader/MultiLoader。其實最後構建的就是實際幹活的對象:HttpGlideUrlLoader
//HttpGlideUrlLoader.java
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
// spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
<div class="se-preview-section-delimiter"></div>
最後的代碼,將新建的HttpUrlFetcher注入給了LoadData,至此,得到一個有效的LoadData和DataFetcher。
3)數據抓取
得到了LoadData後,回到SourceGenerotor中,
public boolean startNext() {
//省略部分代碼...
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;
}
<div class="se-preview-section-delimiter"></div>
通過循環遍歷所有的LoadData,並通過其DataFetcher,就可以進行數據的抓取了。
//HttpUrlFetcher.java
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
<div class="se-preview-section-delimiter"></div>
通過loadDataWithRedirects方法,利用HttpURLConnection就可以抓取到網絡圖片的InputStream,具體不再展開,有興趣可以查看源碼。
最後通過callback.onDataReady(result)將結果回調給SourceGenerator進行下一步處理。
//SourceGenerator.java
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
<div class="se-preview-section-delimiter"></div>
獲取到數據後,分兩種情況,一是需要將圖片緩存到本地硬盤,一種是不需要緩存。Glide配置了多種緩存策略,默認是自動智能切換緩存存儲策略,Glide認爲遠程網絡圖片獲取是昂貴的,所以默認網絡圖片是會緩存原圖的。而本地圖片,包括drawable/assets等是不會緩存原圖的。(當然你也可以重新配置)
那麼這裏獲得的是網絡圖片,所以會進入if中,而else則是直接將結果返回給DecodeJob進行解碼了。
進入if後,會將數據保存起來(留意dataToCache),然後重啓任務。
reschedule後,將會接連回調DecodeJob和EngineJob的reschedule方法,從而重新開啓DecodeJob任務。
//DecodeJob.java
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
//EngineJob.java
@Override
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}
<div class="se-preview-section-delimiter"></div>
由於開啓的是同一個DecodeJob任務,所以整個任務的內容是會繼續得到執行的。結果仍然是回到SourceGenerator的startNext方法中
而這個時候,就會進入上面提到的另一個情況。
//SourceGenerator.java
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//1:先緩存數據
cacheData(data);
}
//2:再重新執行數據轉換和抓取
if (sourceCacheGenerator != null &&
sourceCacheGenerator.startNext()) {
return true;
}
//省略循環遍歷代碼......
}
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);
}
這時dataToCache將不再是null的,所以會將數據緩存到本地硬盤,並啓動另一個加載器DataCacheGenerator,而這個Generator正是用來加載緩存在本地的圖片的。
而DataCacheGenerator和ResourceCacheGenerator的原理與SourceGenerator基本是一致的,只不過一個用來加載原始的本地緩存圖,另一個用來加載處理過的本地緩存。
最後,來總結一下Glide整個的數據轉換與抓取流程:
- Glide利用線程池的方式,將每一個解碼過程都封裝爲一次解碼任務。
- 整個數據抓取過程中,Glide會嘗試從內存到處理過的圖片緩存,再到原圖緩存,最後到遠程圖片等四個地方進行數據加載。(這裏的遠程圖片包括drawable/assets等資源)
- 數據模型轉換時,根據Glide初始化時註冊的模型轉換註冊表,將原始model模型數據轉換爲可能的數據模型,並嘗試使用這些模型來抓取數據,直至抓取到數據,或抓取失敗返回。
- Glide數據轉模型使得Glide有非常好的拓展性和重用性。
- 整個數據轉換和抓取流程非常複雜,但是隻要抓住其中一個源頭,並跟蹤下去,其實還是非常清晰的,也可以看到Glide設計的優雅高明之處。
以上,就是Glide數據模型轉換和抓取的流程分析,下一篇我們將進入Glide的解碼和轉碼源碼分析。
Glide4源碼解析系列
[Glide4源碼解析系列]–1.Glide初始化
[Glide4源碼解析系列]–2.Glide數據模型轉換與數據抓取