Android圖片框架Glide原理深入探索
首先引入依賴
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
下面一行代碼,就是Glide最簡單的使用方式了
Glide.with(this).load(url).into(imageView)
with
首先,我們來看with,其實with的功能就是根據(jù)傳入的context來獲取圖片請求管理器RequestManager,用來啟動和管理圖片請求。
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
context可以傳入app,activity和fragment,這關系著圖片請求的生命周期。通常使用當前頁面的context,這樣當我們打開一個頁面加載圖片,然后退出頁面時,圖片請求會跟隨頁面銷毀而被取消,而不是繼續(xù)加載浪費資源。
當context是app時,獲得的RequestManager是一個全局單例,圖片請求的生命周期會跟隨整個app。
注意:如果with發(fā)生在子線程,不管context是誰,都返回應用級別的RequestManager單例。
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());
}
}
}
return applicationManager;
}當context是Activity時,會創(chuàng)建一個無界面的fragment添加到Activity,用于感知Activity的生命周期,同時創(chuàng)建RequestManager給該fragment持有。
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
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);
// This is a bit of hack, we're going to start the RequestManager, but not the
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the
// Lifecycle might trigger memory leaks. See b/154405040
if (isParentVisible) {
requestManager.onStart();
}
current.setRequestManager(requestManager);
}
return requestManager;
}
load
load方法會得到一個圖片請求構建器RequestBuilder,用來創(chuàng)建圖片請求。
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
into
首先是根據(jù)ImageView的ScaleType,來配置參數(shù)
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
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,
Executors.mainThreadExecutor());
}繼續(xù)跟進into,會創(chuàng)建圖片請求,獲取Target載體已有的請求,對比兩個請求,如果等效,啟動異步請求。然后,圖片載體綁定圖片請求,也就是imageView setTag為request
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// 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;
}繼續(xù)跟進異步請求 requestManager.track(target, request)
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();//開啟圖片請求
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);//如果是暫停狀態(tài),就把請求存起來
}
}
到這里就啟動了圖片請求了,我們繼續(xù)跟進request.begin()
public void begin() {
synchronized (requestLock) {
//......
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//如果有尺寸,開始加載
onSizeReady(overrideWidth, overrideHeight);
} else {
//如果無尺寸就先去獲取
target.getSize(this);
}
//......
}
}
然后繼續(xù)瞧瞧onSizeReady
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
synchronized (requestLock) {
//......
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,
callbackExecutor);
//......
}
}
跟進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,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
//從內(nèi)存加載
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) { //如果內(nèi)存里沒有
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
} private <R> LoadStatus waitForExistingOrStartNewJob(
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,
EngineKey key,
long startTime) {
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
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, callbackExecutor);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}DecodeJob是一個Runnable,它通過一系列的調(diào)用,會來到HttpUrlFetcher的loadData方法
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//獲取輸入流,此處使用的是HttpURLConnection
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//回調(diào)出去
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));
}
}
}
至此,網(wǎng)絡請求結(jié)束,最后把圖片設置上去就行了,在SingleRequest的onResourceReady方法,它會把結(jié)果回調(diào)給Target載體
target.onResourceReady(result, animation);
繼續(xù)跟進它,最終會執(zhí)行setResource,把圖片設置上去
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
原理總結(jié)
with根據(jù)傳入的context來獲取圖片請求管理器RequestManager,當傳入的context是App時,獲得的RequestManager是一個全局單例,圖片請求的生命周期會跟隨這個應用,當傳入的是Activity時,會創(chuàng)建一個無界面的空fragment添加到Activity,用來感知Activity的生命周期。load會得到了一個圖片請求構建器RequestBuilder,用來創(chuàng)建圖片請求。into開啟加載,先會根據(jù)ImageView的ScaleType來配置參數(shù),創(chuàng)建圖片請求,圖片載體綁定圖片請求,然后開啟圖片請求,先從內(nèi)存中加載,如果內(nèi)存里沒有,會創(chuàng)建一個Runnable,通過一系列的調(diào)用,使用HttpURLConnection獲取網(wǎng)絡輸入流,把結(jié)果回調(diào)出去,最后把回調(diào)結(jié)果設置上去就OK了。
緩存
Glide三級緩存原理:讀取一張圖片時,順序是: 弱引用緩存,LruCache,磁盤緩存。
用Glide加載某張圖片時,先去弱引用緩存中尋找圖片,如果有則直接取出來使用,如果沒有,則去LruCache中尋找,如果LruCache中有,則從中取出圖片使用,并將它放入弱引用緩存中,如果都沒有圖片,則從磁盤緩存或網(wǎng)絡中加載圖片。
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key); //從弱引用獲取圖片
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
EngineResource<?> cached = loadFromCache(key); //從LruCache獲取緩存圖片
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}不過,這也會產(chǎn)生一個問題,就是Glide加載圖片時,URL不變但是圖片變了,這種情況會還是以前的舊圖片。因為Glide加載圖片會將圖片緩存到本地,如果url不變則直接讀取緩存不會再網(wǎng)絡加載。
解決方案:
清除緩存讓后臺每次都更改圖片的名字圖片地址選用 ”url?key="+隨機數(shù)這種格式
LruCache
LruCache就是維護一個緩存對象列表,其中對象列表的排列方式是按照訪問順序?qū)崿F(xiàn)的,即一直沒訪問的對象,將放在隊尾,即將被淘汰。而最近訪問的對象將放在隊頭,最后被淘汰。其內(nèi)部維護了一個集合LinkedHashMap,LinkHashMap繼承HashMap,在HashMap的基礎上,新增了雙向鏈表結(jié)構,每次訪問數(shù)據(jù)的時候,會更新被訪問數(shù)據(jù)的鏈表指針,該LinkedHashMap是以訪問順序排序的,當調(diào)用put()方法時,就會在集合中添加元素,判斷緩存是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊尾元素,即近期最少訪問的元素。當調(diào)用get()方法訪問緩存對象時,就會調(diào)用LinkedHashMap的get()方法獲得對應集合元素,同時會更新該元素到隊頭。
那么,問題來了,如果把一個(100 * 100)的圖片放到(800 * 800)的Imageview中會怎么樣呢?由上可知,Glide會為每個不同尺寸的Imageview緩存一張圖片,也就是說不管這張圖片有沒有加載過,只要Imageview的尺寸不一樣,Glide就會重新加載一次,這時候,它會在加載的Imageview之前從網(wǎng)絡上重新下載,然后再緩存。舉個例子,如果一個頁面的Imageview是100 * 100,另一個頁Imageview是800 * 800,它倆展示同一張圖片的話,Glide會下載兩次圖片,并且緩存兩張圖片,因為Glide緩存Key的生成條件之一就是控件的長寬。
除了緩存,Glide還有一點我覺得做的非常好,就是在圖片加載中關閉頁面,此頁面也不會造成內(nèi)存泄漏,因為Glide在加載資源的時候,如果是在 Activity,F(xiàn)ragment 這類有生命周期的組件上進行的話,會創(chuàng)建一個無界面的Fragment加入到FragmentManager之中,感知生命周期,當 Activity,F(xiàn)ragment進入不可見,或者已經(jīng)銷毀的時候,Glide會停止加載資源。但是如果,是在非生命周期的組件上進行時,會采用Application的生命周期貫穿整個應用,此時只有在應用程序關閉的時候才會停止加載。
到此這篇關于Android Glide原理深入探索的文章就介紹到這了,更多相關Android Glide內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android canvas drawBitmap方法詳解及實例
這篇文章主要介紹了 Android canvas drawBitmap方法詳解及實例的相關資料,需要的朋友可以參考下2017-01-01
Android中使用GridView實現(xiàn)仿微信圖片上傳功能(附源代碼)
由于工作要求最近在使用GridView完成圖片的批量上傳功能,我的例子當中包含仿微信圖片上傳、拍照、本地選擇、相片裁剪等功能,如果有需要的朋友可以看一下2017-08-08
Android 使用URLConnection下載音頻文件的方法
有時候我們會需要下載音頻文件。這里提供一種思路,將在線音頻文件通過流寫到本地文件中。需要的朋友可以參考下2019-09-09
Android應用開發(fā)中模擬按下HOME鍵的效果(實現(xiàn)代碼)
Android應用開發(fā)中, 有一種場景,就是我們不希望用戶直接按Back鍵退出Activity,而是希望應用隱藏到后臺,類似于按Home鍵的效果2013-05-05
Android Gridview布局出現(xiàn)滾動條或組件沖突解決方法
這篇文章主要介紹了Android Gridview布局出現(xiàn)滾動條或組件沖突解決方法,GridView是一個在二維可滾動的網(wǎng)格中展示內(nèi)容的控件。網(wǎng)格中的內(nèi)容通過使用adapter自動插入到布局中2022-07-07
在Android中通過Intent使用Bundle傳遞對象的使用方法
這篇文章主要介紹了在Android中通過Intent使用Bundle傳遞對象的使用方法,詳細介紹Intent使用Bundle傳遞對象的方法。有需要的可以了解一下。2016-11-11
Android 中通過實現(xiàn)線程更新Progressdialog (對話進度條)
這篇文章主要介紹了Android 中通過實現(xiàn)線程更新Progressdialog (對話進度條)的相關資料,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2016-11-11
Android編程中聊天頁面背景圖片、標題欄由于鍵盤引起問題的解決方法
這篇文章主要介紹了Android編程中聊天頁面背景圖片、標題欄由于鍵盤引起問題的解決方法,針對鍵盤彈出時標題欄及背景圖片異常的相關解決方法,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10

