Glide是一個Android的圖片加載和緩存庫,它主要專一於大量圖片的流暢加載。是google所推薦的圖片加載庫,做者是bumptech。這個庫被普遍運用在google的開源項目中,包括2014年的google I/O大會上發佈的官方App。html
WIKI地址:WIKI官網java
Github地址:Githubandroid
一、多樣化媒體加載git
Glide 不只是一個圖片緩存,它支持 Gif、WebP等格式github
二、生命週期集成算法
咱們能夠更加高效的使用Glide提供的方式進行綁定,這樣能夠更好的讓加載圖片的請求的生命週期動態管理起來設計模式
三、高效的緩存策略緩存
四、 提供豐富的圖片轉換Api,支持圓形裁剪、平滑顯示等特性bash
一、gradle引入庫,implementation 'com.github.bumptech.glide:glide:4.7.1'服務器
二、配置Glide的with load apply into等方法
public void loadImageView(ImageView view,String url){
//屬性的配置
RequestOptions options = new RequestOptions()
//加載成功以前佔位圖
.placeholder(R.mipmap.ic_launcher)
//加載錯誤以後的錯誤圖
.error(R.mipmap.ic_launcher)
//指定圖片的尺寸
.override(1000,800)
//指定圖片的縮放類型爲fitCenter (等比例縮放圖片,寬或者是高等於ImageView的寬或者是高。)
.fitCenter()
//指定圖片的縮放類型爲centerCrop (等比例縮放圖片,直到圖片的狂高都大於等於ImageView的寬度,而後截取中間的顯示。)
.centerCrop()
.circleCrop()//指定圖片的縮放類型爲centerCrop (圓形)
//跳過內存緩存
.skipMemoryCache(true)
//緩存全部版本的圖像
.diskCacheStrategy(DiskCacheStrategy.ALL)
//跳過磁盤緩存
.diskCacheStrategy(DiskCacheStrategy.NONE)
//只緩存原來分辨率的圖片
.diskCacheStrategy(DiskCacheStrategy.DATA)
//只緩存最終的圖片
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.priority(Priority.HIGH)
;
//加載圖片
Glide.with(getApplicationContext())
.load(url)
.apply(options)
.into(view);
}
複製代碼
三、執行ImageView的加載
loadImageView(ivPic,"http://b.hiphotos.baidu.com/image/pic/item/d52a2834349b033bda94010519ce36d3d439bdd5.jpg");
複製代碼
詳細的使用教程及option的配置,推薦參考
Android圖片加載框架最全解析(八),帶你全面瞭解Glide 4的用法
類型 | 說明 |
---|---|
Data | 表明原始的,未修改過的資源,對應dataClass |
Resource | 修改過的資源,對應resourceClass |
Transcoder | 資源轉換器,好比BitmapBytesTranscoder(Bitmap轉換爲Bytes),GifDrawableBytesTranscoder |
ResourceEncoder | 持久化數據的接口,注意,該類並不與decoder相對應,而是用於本地緩存的接口 |
ResourceDecoder | 數據解碼器,好比ByteBufferGifDecoder(將ByteBuffer轉換爲Gif),StreamBitmapDecoder(Stream轉換爲Bitmap) |
ResourceTranscoder | 資源轉換器,將給定的資源類型,轉換爲另外一種資源類型,好比將Bitmap轉換爲Drawable,Bitmap轉換爲Bytes |
Transformation | 好比對圖片進行FitCenter,CircleCrop,CenterCrop的transformation,或者根據給定寬高對Bitmap進行處理的BitmapDrawableTransformation |
Target | request的載體,各類資源對應的加載類,含有生命週期的回調方法,方便開發人員進行相應的準備以及資源回收工做 |
一、構建Request,實現類爲SingleRequest,用於發起一個加載的請求
二、經過EngineJob和DecodeJob負責任務建立,發起,回調,資源的管理
三、根據請求的資源類型,最後匹配對應的DateFetcher進行Data數據的獲取
四、獲取數據進行相應的緩存配置
五、根據原始數據Data進行解碼及轉換,生成最終須要顯示的Resource
六、經過回調Target對應的方法,最後進行圖片的顯示
類 | 功能說明 |
---|---|
Glide | 向外暴露單例靜態接口,構建Request,配置資源類型,緩存策略,圖片處理等,能夠直接經過該類完成簡單的圖片請求和填充。內部持有一些內存變量BitmapPool,MemoryCache,ByteArrayPool,便於低內存狀況時自動清理內存 |
RequestManagerRetriever | 用於建立RequestManager對象,並與Context作相應的生命週期綁定 |
RequestManagerFragment | Glide向Activity或Fragment中添加的空Fragment,用於控制綁定生命週期 |
LifecycleListener | 用於監聽Activity或者Fragment的生命週期方法的接口 |
RequestManager | 用戶管理及發起請求,支持resume、pause、clear等操做 |
RequestBuilder | 建立請求,資源類型配置,縮略圖配置,以及經過BaseRequestOptions進行一些默認圖,圖片處理的配置 |
Engine | 任務建立,發起,回調,管理存活和緩存的資源 |
EngineJob | 調度DecodeJob,添加,移除資源回調,並notify回調 |
DecodeJob | 實現了Runnable接口,調度任務的核心類,整個請求的繁重工做都在這裏完成:處理來自緩存或者原始的資源,應用轉換動畫以及transcode。負責根據緩存類型獲取不一樣的Generator加載數據,數據加載成功後回調DecodeJob的onDataFetcherReady方法對資源進行處理 |
ResourceCacheGenerator | 嘗試從修改過的資源緩存中獲取,若是緩存未命中,嘗試從DATA_CACHE中獲取 |
DataCacheGenerator | 嘗試從未修改過的本地緩存中獲取數據,若是緩存未命中則嘗試從SourceGenerator中獲取 |
SourceGenerator | 從原始的資源中獲取,多是服務器,也多是本地的一些原始資源 |
DataFetcher | 數據加載接口,經過loadData加載數據並執行對應的回調 |
LoadPath | 根據給定的數據類型的DataFetcher嘗試獲取數據,而後嘗試經過一個或多個decodePath進行decode |
DecodePath | 根據指定的數據類型對resource進行decode和transcode |
Registry | 管理組件(數據類型+數據處理)的註冊 |
ModelLoaderRegistry | 註冊全部數據加載的loader |
ResourceDecoderRegistry | 註冊全部資源轉換的decoder |
TranscoderRegistry | 註冊全部對decoder以後進行特殊處理的transcoder |
ResourceEncoderRegistry | 註冊全部持久化resource(處理過的資源)數據的encoder |
EncoderRegistry | 註冊全部的持久化原始數據的encoder |
先貼一下流程圖,建議經過源碼結合流程圖進行分析,下面再分步驟進行分析。
下面主要從with()、load()、into()3個方法進行分析。
一、with()方法,最後會返回一個RequestManger對象用於發起Request。
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
複製代碼
二、getRetriever(context),最後返回一個RequestManagerRetriever對象用於生成RequestManager。
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
複製代碼
這裏Glide.get(contenxt),會對glide進行初始化,模塊掃描、組件註冊等工做。
三、RequestManagerRetriever的get(context)方法
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
複製代碼
這裏主要根據context的類型,去建立不一樣的RequestManager的對象,綁定生命週期。 若是context是Application對象的話,則調用,綁定了ApplicationLifecycle。
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { // Normally pause/resume is taken care of by the fragment we add to the fragment or // activity. However, in this case since the manager attached to the application will not // receive lifecycle events, we must force the manager to start resumed using // ApplicationLifecycle. // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); } } } 複製代碼
四、若是context是Activity或Fragment的話,則會調用supportFragmentGet、FragmentGetd方法,建立RequestManagerFragment進行生命週期綁定ActivityFragmentLifecycle。
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
複製代碼
總結一下:with()方法,最後會返回一個根據context類型綁定生命週期的RequestManger對象。
1.load()方法最後會生成一個RequestBuilder對象,用於構建Request,並執行請求操做.。首先會經過as方法生成一個RequestBuilder對象
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
複製代碼
二、執行load()方法
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
複製代碼
三、執行loadGeneric方法
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
複製代碼
保存load傳進行的參數,並設置isModelSet爲true
四、設置請求的配置參數,apply(RequestOptions requestOptions)
public RequestBuilder<TranscodeType> apply(@NonNull RequestOptions requestOptions) {
Preconditions.checkNotNull(requestOptions);
this.requestOptions = getMutableOptions().apply(requestOptions);
return this;
}
複製代碼
總結一下load()方法,最後會返回一個RequestBuilder對象,經過apply設置請求的參數,用於構建Request。
into()是整個過程最複雜的一步,簡單來講就是經過緩存策略及註冊的Moderload,最終去加載源數據Data,並進行轉換爲配置的Resource,最後顯示再Target上。
一、RequestBuilder的into方法實現
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
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)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, 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 un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
複製代碼
二、會執行BuildRequest()生成Request對象。通過一系列的調用,最後執行的方法以下,會返回一個SingleRequest對象實例
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());
}
複製代碼
三、接着會調用 requestManager.track(target, request);實現以下:
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
複製代碼
四、這裏涉及到一個新類RequestTracker,用於管理Request的生命週期。runRequest實現以下:
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
複製代碼
五、最後會調用Request的begin()方法開始執行請求,實現以下:
public void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
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());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
複製代碼
這裏主要會根據status進行判斷,若是COMPLETE已完成,則直接回調 onResourceReady,若是是WAITING_FOR_SIZE,則會執行onSizeReady方法,咱們都知道Glide會根據實際顯示的View寬高去生成最後的Resource進行顯示。
六、onResourceReady實現以下:
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (IS_VERBOSE_LOGGABLE) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
if (IS_VERBOSE_LOGGABLE) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
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);
// This is a hack that's only useful for testing right now where loads complete synchronously // even though under any executor running on any thread but the main thread, the load would // have completed asynchronously. if (status != Status.RUNNING) { loadStatus = null; } if (IS_VERBOSE_LOGGABLE) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); } } 複製代碼
最終會經過engine的load方法去執行請求,後續的緩存策略、數據加載、圖片轉換都是在下面步驟執行
七、具體看Engine的load方法實現
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 = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
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 (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
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;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
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 (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
複製代碼
這裏首先經過構建EngineKey,判斷內存緩存中是否命中。接着判斷jobs隊列中是否已存在該任務。不然會構建EngineJob、DecodeJob,並經過engineJob.start(decodeJob),經過線程池去執行DecodeJob任務。DecodeJob實現了Runnable接口,因此咱們接着分析DecodeJob的run方法
八、DecodeJob的run方法最後執行了runWrapped方法,實現以下:
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);
}
}
複製代碼
根據不一樣的runReason執行不一樣的任務,共兩種任務類型:
runGenerators():load數據
decodeFromRetrievedData():處理已經load到的數據
RunReason再次執行任務的緣由,三種枚舉值: INITIALIZE:第一次調度任務
WITCH_TO_SOURCE_SERVICE:本地緩存策略失敗,嘗試從新獲取數據,兩種狀況;當stage爲Stage.SOURCE,或者獲取數據失敗而且執行和回調發生在了不一樣的線程
DECODE_DATA:獲取數據成功,但執行和回調不在同一線程,但願回到本身的線程去處理數據。
九、getNextStage()是獲取加載資源的策略,一共5種策略: INITIALIZE,RESOURCE_CACHE,DATA_CACHE,SOURCE,FINISHED
其中加載數據的策略有三種: RESOURCE_CACHE,DATA_CACHE,SOURCE, 分別對應的Generator:
ResourceCacheGenerator :嘗試從修改過的資源緩存中獲取,若是緩存未命中,嘗試從DATA_CACHE中獲取
DataCacheGenerator :嘗試從未修改過的本地緩存中獲取數據,若是緩存未命中則嘗試從SourceGenerator中獲取
SourceGenerator :從原始的資源中獲取,多是服務器,也多是本地的一些原始資源 策略的配置在DiskCacheStrategy。開發者可經過BaseRequestOptions設置: ALL NONE DATA RESOURCE AUTOMATIC(默認方式,依賴於DataFetcher的數據源和ResourceEncoder的EncodeStrategy)
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);
}
}
複製代碼
十、getNextGenerator,根據Stage獲取到相應的Generator後會執行currentGenerator.startNext(),若是中途startNext返回true,則直接回調,不然最終會獲得SOURCE的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(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. } 複製代碼
十一、這裏咱們分析最後SourceGenerator的startNext的執行,實現以下:
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;
}
複製代碼
最後會經過Glide初始化時,Register註冊的ModelLoader去執行對應的loadData方法,最後回調onDataFetcherReady(),獲取獲得DataSource,並將 runReason = RunReason.DECODE_DATA。觸發調用decodeFromRetrievedData()進行源數據的轉換
十二、decodeFromRetrievedData,獲取數據成功後,進行處理,內部調用的是runLoadPath(Data data, DataSource dataSource,LoadPath<Data, ResourceType, R> path),decode完成後的回調,對decode的資源進行transform。
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
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();
}
}
@SuppressWarnings("unchecked")
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
複製代碼
1三、decodeFromRetrievedData()中,數據decode和transform後會執行notifyEncodeAndRelease方法,在該方法中調用 notifyComplete(result, dataSource),接着調用callback.onResourceReady,實現以下:
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
複製代碼
1四、經過Handler將回調切換到主線程, 最後調用EngineJob的handleResultOnMainThread方法,實現以下:
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
// Hold on to resource for duration of request so we don't recycle it in the middle of // notifying if it synchronously released by one of the callbacks. engineResource.acquire(); listener.onEngineJobComplete(this, key, engineResource); //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = cbs.size(); i < size; i++) { ResourceCallback cb = cbs.get(i); if (!isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource, dataSource); } } // Our request is complete, so we can release the resource. engineResource.release(); release(false /*isRemovedFromQueue*/); } 複製代碼
1五、進行相關的資源清理後,最後調用SingleRequest的onResourceReady(Resource<?> resource, DataSource dataSource) 方法,最後調用target.onResourceReady(result, animation)方法,實現資源的顯示,代碼以下:
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (glideContext.getLogLevel() <= Log.DEBUG) {
Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
+ dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
+ LogTime.getElapsedMillis(startTime) + " ms");
}
isCallingCallbacks = true;
try {
if ((requestListener == null
|| !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
&& (targetListener == null
|| !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
Transition<? super R> animation =
animationFactory.build(dataSource, isFirstResource);
target.onResourceReady(result, animation);
}
} finally {
isCallingCallbacks = false;
}
notifyLoadSuccess();
}
複製代碼
Glide在執行with的階段,會根據context的類型,將Glide的Request請求與context類型進行綁定。Application類型爲整個應用的生命週期。Fragment及Activity類型,經過巧妙的設計一個RequestManagerFragment,加入到Activity或Fragment當中,從而實現生命週期的監聽。
一、Application的綁定爲ApplicationLifecycle,與App的生命週期一致
@Override
public void addListener(@NonNull LifecycleListener listener) {
listener.onStart();
}
@Override
public void removeListener(@NonNull LifecycleListener listener) {
// Do nothing.
}
複製代碼
二、Activity或Fragment的綁定爲ActivityFragmentLifecycle,與宿主對應的生命週期一致。
public interface LifecycleListener {
/**
* Callback for when {@link android.app.Fragment#onStart()}} or {@link
* android.app.Activity#onStart()} is called.
*/
void onStart();
/**
* Callback for when {@link android.app.Fragment#onStop()}} or {@link
* android.app.Activity#onStop()}} is called.
*/
void onStop();
/**
* Callback for when {@link android.app.Fragment#onDestroy()}} or {@link
* android.app.Activity#onDestroy()} is called.
*/
void onDestroy();
}
複製代碼
三、在RequestManger中實現了監聽接口的註冊,代碼以下:
private final Runnable addSelfToLifecycle = new Runnable() {
@Override
public void run() {
lifecycle.addListener(RequestManager.this);
}
};
複製代碼
這裏注意,一個頁面擁有一個RequestManagerFragment,RequestManagerFragment會持有RequestManger的引用。一個頁面發起多個Glide顯示圖片請求,會優先從Fragment中獲取RequestManger,不會重複建立多個RequestManger對象。
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
複製代碼
四、RequestManger中綁定的回調執行
/**
* Lifecycle callback that registers for connectivity events (if the
* android.permission.ACCESS_NETWORK_STATE permission is present) and restarts failed or paused
* requests.
*/
@Override
public void onStart() {
resumeRequests();
targetTracker.onStart();
}
/**
* Lifecycle callback that unregisters for connectivity events (if the
* android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads.
*/
@Override
public void onStop() {
pauseRequests();
targetTracker.onStop();
}
/**
* Lifecycle callback that cancels all in progress requests and clears and recycles resources for
* all completed requests.
*/
@Override
public void onDestroy() {
targetTracker.onDestroy();
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
targetTracker.clear();
requestTracker.clearRequests();
lifecycle.removeListener(this);
lifecycle.removeListener(connectivityMonitor);
mainHandler.removeCallbacks(addSelfToLifecycle);
glide.unregisterRequestManager(this);
}
複製代碼
從實現可知,當Activity或Fragment退到後臺時,會調用pauseRequests()暫停請求,回到前臺時會從新執行請求,當頁面銷燬時,會進行對應的資源清理及回收。
Glide的緩存使用內存緩存及硬盤緩存進行處理。
緩存 | 說明 |
---|---|
ActiveResources | ActiveResources是一個以弱引用資源爲value。用於緩存正在使用的資源 |
MemoryCache | MemoryCache是使用LruResourceCache實現,用於緩存非正在使用的資源 |
DiskCache | 進行資源磁盤緩存 |
Http | 經過網絡地址,從服務端加載資源文件 |
假如用戶配置了使用內存緩存及磁盤緩存,則主要的加載實現流程以下:
一、當發起Request時,首先會從ActiveResources中進行緩存查找。若是命中則返回顯示,若是不命中,則從MemoryCache中獲取。當資源從ActiveResources中移除後,加入到MemoryCache中
二、當在MemoryCache中命中時,則會將資源加入到ActiveResources中,並在該Cache中移除,若是不命中則會嘗試從磁盤緩存中進行加載
三、根據用於配置的策略,若是在磁盤緩存中命中,則會返回,並將資源緩存到ActiveResources當中,若是不命中,則會將進行網絡的請求
四、根據ModelLoader的配置實現,從網絡中加載資源,並根據配置,緩存到磁盤及內存緩存中
根據流程分析,咱們知道Key的生成在Engine的load方法中,具體的實現以下:
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
複製代碼
可見爲了兼容複雜的資源轉換,保證key的惟一性,包含了很是多的參數進行構建。主要有model(目標地址)、signature(設置的簽名)、圖片的width、heigh、資源的轉換配置等。
根據上面的簡介,咱們能夠知道,Glide主要的內存緩存策略採用了2級緩存,爲ActiveResources和MemoryCache。下面咱們從源碼的角度分析這2個緩存的機制。
一、 根據源碼,咱們可知內存採用了一個HashMap進行內存的緩存,使用了弱引用ResourceWeakReference持有了Resource
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
複製代碼
二、當獲取資源時,主要採用get方法進行獲取,實現以下:
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;
}
複製代碼
若是命中,就返回資源。這裏注意,若是當active==null,引用被回收時,會調用cleanupActiveReference方法,實現以下:
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
Util.assertMainThread();
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
EngineResource<?> newResource =
new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
newResource.setResourceListener(ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}
複製代碼
若是ref.resource!=null,則會從新生成一個EngineResource對象,並回調onResourceReleased方法,咱們看具體的實現以下:
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
複製代碼
從源碼可知,會調用deactivate方法,從activeResources中移除,而後加入到MemoryCache中。
三、寫入資源
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();
}
}
複製代碼
EngineResource中主要爲了一個acquired變量
private int acquired;
複製代碼
當資源被使用時,會調用acquire,將變量值+1
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;
}
複製代碼
當資源被釋放時,會調用release()方法
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);
}
}
複製代碼
這裏注意,當acquired == 0,代表資源沒有被使用時,則會調用onResourceReleased,將資源存儲到MemoryCache中。這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存的功能。
Glide在Build的過程當中會建立具體的MemoryCache對象,具體的實現以下:
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
複製代碼
從源碼可知,MemoryCache的主要實現是採用了LRU算法,咱們具體查看LruResourceCache的實現。發現其繼承與LruCache,LruCache的關鍵實現以下:
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
複製代碼
從源碼可知,Glide的內存緩存的LRU算法實現主要是使用了LinkedHashMap。
這裏詳細的說明可參考: 如何用LinkedHashMap實現LRU緩存算法
Glide內部維護了一個BitmapPool池,用於Bitmap的複用,可優化GC的回收。 在GlideBuilder中進行了實例,實現以下:
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
複製代碼
咱們具體看LruBitmapPool的實現代碼
一、 put
@Override
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())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Reject bitmap from pool"
+ ", bitmap: " + strategy.logBitmap(bitmap)
+ ", is mutable: " + bitmap.isMutable()
+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));
}
bitmap.recycle();
return;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();
evict();
}
複製代碼
作了一系列的非空、回收判斷後,最後將bitmap加入到strategy中,加入到緩存中
二、get
@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
// Bitmaps in the pool contain random data that in some cases must be cleared for an image
// to be rendered correctly. we shouldn't force all consumers to independently erase the // contents individually, so we do so here. See issue #131. result.eraseColor(Color.TRANSPARENT); } else { result = createBitmap(width, height, config); } return result; } 複製代碼
當須要生成Bitmap中,根據寬高及config從池中獲取,若是沒有,則調用createBtimao建立一個。若是命中,則會提取出Bitmap,而後將像素都設置爲透明,同時從池中移除,並返回。
總結一下
glide採用了2級的內存緩存,activeResources是一個以弱引用資源爲value,的map,memory是使用LruResourceCache實現的。就是activeResources是一個隨時有可能被回收資源。它存在的意義在於,memory的強引用的頻繁讀寫也有可能形成內存激增頻繁GC,而形成內存抖動。資源在使用的過程當中將會保存在activeResources中,而activeResources是弱引用的,能夠隨時被系統回收,不會形成內存泄漏和過多的使用
Glide緩存的資源分爲兩種(1,原圖(SOURCE)原始圖片 2,處理圖(RESULT)通過壓縮和變形等轉化的圖片)
硬盤緩存分爲五種,具體看一面。能夠經過調用diskCacheStrategy()方法並傳入五種不一樣的參數
1,DiskCacheStrategy.NONE// 表示不緩存任何內容
2,DiskCacheStrategy.DATA// 表示只緩存原始圖片
3,DiskCacheStrategy.RESOURCE// 表示只緩存轉換事後的圖片
4,DiskCacheStrategy.ALL // 表示既緩存原始圖片,也緩存轉換事後的圖片
5,DiskCacheStrategy.AUTOMATIC//表示讓Glide根據圖片資源智能地選擇使用哪種緩存策略(默認選項)
一、根據上述的流程分析,咱們知道具體磁盤緩存在DecodeJob中執行,當任務開始時,調用了runWrapped()方法,接着會調用getNextStage,這裏會獲取磁盤的加載策略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);
}
}
複製代碼
接着會調用getNextGenerator方法,實現以下:
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);
}
}
複製代碼
這裏獲取下一步執行的策略,一共5種策略: INITIALIZE,RESOURCE_CACHE,DATA_CACHE,SOURCE,FINISHED
其中加載數據的策略有三種: RESOURCE_CACHE,DATA_CACHE,SOURCE, 分別對應的Generator:
ResourceCacheGenerator :嘗試從修改過的資源緩存中獲取,若是緩存未命中,嘗試從DATA_CACHE中獲取
DataCacheGenerator :嘗試從未修改過的本地緩存中獲取數據,若是緩存未命中則嘗試從SourceGenerator中獲取
SourceGenerator :從原始的資源中獲取,多是服務器,也多是本地的一些原始資源
接着調用具體的Generator的startNext方法。磁盤的緩存獲取實如今這個方法中獲取。 這裏ResourceCacheGenerator的關鍵緩存獲取代碼以下:
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.
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
複製代碼
DataCacheGenerator的關鍵緩存獲取代碼以下:
Key sourceId = cacheKeys.get(sourceIdIndex);
// PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
// and the actions it performs are much more expensive than a single allocation.
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
複製代碼
一、Data數據的緩存
從服務端獲取數據的主要實如今SourceGenerator中,咱們查看源碼onDataReady可知其中判斷了isDataCacheable()會將數據賦值到dataToCache中。從新觸發reschedule();
@Override
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);
}
}
複製代碼
當再次出發startNext,關鍵實現以下:
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
複製代碼
其中的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);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
複製代碼
二、Resource數據的緩存
根據上述的流程分析,再DecodeJob中的onResourceDecoded回調中,關鍵的實現以下:
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;
}
複製代碼
會初始化DeferredEncodeManager對象。接着會執行到notifyEncodeAndRelease()方法,其中關鍵的實現以下:
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
複製代碼
在encode中進行了resource數據的緩存,代碼以下:
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();
}
}
複製代碼
一、在Glide的Build方法中,咱們能夠看到磁盤緩存的工廠實例,代碼以下:
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
複製代碼
二、InternalCacheDiskCacheFactory繼承了DiskLruCacheFactory,工廠關鍵的build方法以下:
@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);
}
複製代碼
三、可見實際的磁盤緩存對象爲DiskLruCacheWrapper類,咱們看對應的get、put方法,實現以下:
@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; } @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. 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); } } 複製代碼
四、最終可知磁盤緩存的實現爲DiskLruCache,關於DiskLruCache緩存可參考以下博文
經過上述的流程分析,咱們可知最後的網絡加載在SourceGenerator中的startNext()方法,經過初始註冊的ModelLoader對應的DataFetcher去加載數據。咱們以load一個GlideUrl地址來分析
在Glide的構造函數中,Register會註冊ModelLoader,代碼實現以下:
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
複製代碼
在SourceGenerator中的startNext()方法中會循環匹配出對應的ModelLoader,實現以下:
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);
}
}
複製代碼
咱們查看HttpGlideUrlLoader,其最後的LoadData實現爲HttpUrlFetcher,具體代碼以下:
@Override
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));
}
複製代碼
HttpUrlFetcher具體加載網絡的數據的實現以下:
@Override
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));
}
}
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = urlConnection.getInputStream(); if (isCancelled) { return null; } final int statusCode = urlConnection.getResponseCode(); if (isHttpOk(statusCode)) { return getStreamForSuccessfulRequest(urlConnection); } else if (isHttpRedirect(statusCode)) { String redirectUrlString = urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { throw new HttpException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); // Closing the stream specifically is required to avoid leaking ResponseBodys in addition // to disconnecting the url connection below. See #2352. cleanup(); return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else if (statusCode == INVALID_STATUS_CODE) { throw new HttpException(statusCode); } else { throw new HttpException(urlConnection.getResponseMessage(), statusCode); } } 複製代碼
經過分析可知,Glide默認的網絡加載使用的是urlConnection。固然咱們也能夠經過自定義ModelLoader,使用okhttp、volley等的網絡框架進行加載。
具體可參考博文 Glide 4.x添加自定義組件原理
一、建造者模式
Glide對象的建立使用Build模式,將複雜對象的建立和表示分離,調用者不須要知道複雜的建立過程,使用Build的相關方法進行配置建立對象。
二、外觀模式
Glide對外提供了統一的調度,屏蔽了內部的實現,使得使用該網絡庫簡單便捷。
三、策略模式
關於DecodeJob中的DataFetcherGenerator資源獲取,採用了策略模式,將數據加載的不一樣算法進行封裝。
四、工廠模式
ModelLoader的建立使用了ModelLoaderFactory、Engine中的EngineJobFactory、DiskLruCacheFactory等
Glide正由於其強大的功能,高效的運行機制,因此源碼的實現很是複雜。在學習的過程當中也遇到不少的困難,最終仍是一步一步堅持下來了。有時候放棄就是一瞬間的念頭,但堅持下來,終究會有收穫。
Android圖片加載框架最全解析(八),帶你全面瞭解Glide 4的用法
歡迎關注個人我的公衆號
微信搜索:一碼一浮生,或者搜索公衆號ID:life2code