Android圖片框架Glide原理深入探索
首先引入依賴
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
下面一行代碼,就是Glide最簡(jiǎn)單的使用方式了
Glide.with(this).load(url).into(imageView)
with
首先,我們來看with,其實(shí)with的功能就是根據(jù)傳入的context來獲取圖片請(qǐng)求管理器RequestManager,用來啟動(dòng)和管理圖片請(qǐng)求。
public static RequestManager with(@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity); }
context可以傳入app,activity和fragment,這關(guān)系著圖片請(qǐng)求的生命周期。通常使用當(dāng)前頁面的context,這樣當(dāng)我們打開一個(gè)頁面加載圖片,然后退出頁面時(shí),圖片請(qǐng)求會(huì)跟隨頁面銷毀而被取消,而不是繼續(xù)加載浪費(fèi)資源。
當(dāng)context是app時(shí),獲得的RequestManager是一個(gè)全局單例,圖片請(qǐng)求的生命周期會(huì)跟隨整個(gè)app。
注意:如果with發(fā)生在子線程,不管context是誰,都返回應(yīng)用級(jí)別的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; }
當(dāng)context是Activity時(shí),會(huì)創(chuàng)建一個(gè)無界面的fragment添加到Activity,用于感知Activity的生命周期,同時(shí)創(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方法會(huì)得到一個(gè)圖片請(qǐng)求構(gòu)建器RequestBuilder,用來創(chuàng)建圖片請(qǐ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ù)跟進(jìn)into,會(huì)創(chuàng)建圖片請(qǐng)求,獲取Target載體已有的請(qǐng)求,對(duì)比兩個(gè)請(qǐng)求,如果等效,啟動(dòng)異步請(qǐng)求。然后,圖片載體綁定圖片請(qǐng)求,也就是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ù)跟進(jìn)異步請(qǐng)求 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();//開啟圖片請(qǐng)求 } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request);//如果是暫停狀態(tài),就把請(qǐng)求存起來 } }
到這里就啟動(dòng)了圖片請(qǐng)求了,我們繼續(xù)跟進(jìn)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); //...... } }
跟進(jìn)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是一個(gè)Runnable,它通過一系列的調(diào)用,會(huì)來到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)絡(luò)請(qǐng)求結(jié)束,最后把圖片設(shè)置上去就行了,在SingleRequest的onResourceReady方法,它會(huì)把結(jié)果回調(diào)給Target載體
target.onResourceReady(result, animation);
繼續(xù)跟進(jìn)它,最終會(huì)執(zhí)行setResource,把圖片設(shè)置上去
protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); }
原理總結(jié)
with根據(jù)傳入的context來獲取圖片請(qǐng)求管理器RequestManager,當(dāng)傳入的context是App時(shí),獲得的RequestManager是一個(gè)全局單例,圖片請(qǐng)求的生命周期會(huì)跟隨這個(gè)應(yīng)用,當(dāng)傳入的是Activity時(shí),會(huì)創(chuàng)建一個(gè)無界面的空fragment添加到Activity,用來感知Activity的生命周期。load會(huì)得到了一個(gè)圖片請(qǐng)求構(gòu)建器RequestBuilder,用來創(chuàng)建圖片請(qǐng)求。into開啟加載,先會(huì)根據(jù)ImageView的ScaleType來配置參數(shù),創(chuàng)建圖片請(qǐng)求,圖片載體綁定圖片請(qǐng)求,然后開啟圖片請(qǐng)求,先從內(nèi)存中加載,如果內(nèi)存里沒有,會(huì)創(chuàng)建一個(gè)Runnable,通過一系列的調(diào)用,使用HttpURLConnection獲取網(wǎng)絡(luò)輸入流,把結(jié)果回調(diào)出去,最后把回調(diào)結(jié)果設(shè)置上去就OK了。
緩存
Glide三級(jí)緩存原理:讀取一張圖片時(shí),順序是: 弱引用緩存,LruCache,磁盤緩存。
用Glide加載某張圖片時(shí),先去弱引用緩存中尋找圖片,如果有則直接取出來使用,如果沒有,則去LruCache中尋找,如果LruCache中有,則從中取出圖片使用,并將它放入弱引用緩存中,如果都沒有圖片,則從磁盤緩存或網(wǎng)絡(luò)中加載圖片。
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; }
不過,這也會(huì)產(chǎn)生一個(gè)問題,就是Glide加載圖片時(shí),URL不變但是圖片變了,這種情況會(huì)還是以前的舊圖片。因?yàn)镚lide加載圖片會(huì)將圖片緩存到本地,如果url不變則直接讀取緩存不會(huì)再網(wǎng)絡(luò)加載。
解決方案:
清除緩存讓后臺(tái)每次都更改圖片的名字圖片地址選用 ”url?key="+隨機(jī)數(shù)這種格式
LruCache
LruCache就是維護(hù)一個(gè)緩存對(duì)象列表,其中對(duì)象列表的排列方式是按照訪問順序?qū)崿F(xiàn)的,即一直沒訪問的對(duì)象,將放在隊(duì)尾,即將被淘汰。而最近訪問的對(duì)象將放在隊(duì)頭,最后被淘汰。其內(nèi)部維護(hù)了一個(gè)集合LinkedHashMap,LinkHashMap繼承HashMap,在HashMap的基礎(chǔ)上,新增了雙向鏈表結(jié)構(gòu),每次訪問數(shù)據(jù)的時(shí)候,會(huì)更新被訪問數(shù)據(jù)的鏈表指針,該LinkedHashMap是以訪問順序排序的,當(dāng)調(diào)用put()方法時(shí),就會(huì)在集合中添加元素,判斷緩存是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊(duì)尾元素,即近期最少訪問的元素。當(dāng)調(diào)用get()方法訪問緩存對(duì)象時(shí),就會(huì)調(diào)用LinkedHashMap的get()方法獲得對(duì)應(yīng)集合元素,同時(shí)會(huì)更新該元素到隊(duì)頭。
那么,問題來了,如果把一個(gè)(100 * 100)的圖片放到(800 * 800)的Imageview中會(huì)怎么樣呢?由上可知,Glide會(huì)為每個(gè)不同尺寸的Imageview緩存一張圖片,也就是說不管這張圖片有沒有加載過,只要Imageview的尺寸不一樣,Glide就會(huì)重新加載一次,這時(shí)候,它會(huì)在加載的Imageview之前從網(wǎng)絡(luò)上重新下載,然后再緩存。舉個(gè)例子,如果一個(gè)頁面的Imageview是100 * 100,另一個(gè)頁Imageview是800 * 800,它倆展示同一張圖片的話,Glide會(huì)下載兩次圖片,并且緩存兩張圖片,因?yàn)镚lide緩存Key的生成條件之一就是控件的長(zhǎng)寬。
除了緩存,Glide還有一點(diǎn)我覺得做的非常好,就是在圖片加載中關(guān)閉頁面,此頁面也不會(huì)造成內(nèi)存泄漏,因?yàn)镚lide在加載資源的時(shí)候,如果是在 Activity,F(xiàn)ragment 這類有生命周期的組件上進(jìn)行的話,會(huì)創(chuàng)建一個(gè)無界面的Fragment加入到FragmentManager之中,感知生命周期,當(dāng) Activity,F(xiàn)ragment進(jìn)入不可見,或者已經(jīng)銷毀的時(shí)候,Glide會(huì)停止加載資源。但是如果,是在非生命周期的組件上進(jìn)行時(shí),會(huì)采用Application的生命周期貫穿整個(gè)應(yīng)用,此時(shí)只有在應(yīng)用程序關(guān)閉的時(shí)候才會(huì)停止加載。
到此這篇關(guān)于Android Glide原理深入探索的文章就介紹到這了,更多相關(guān)Android Glide內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android canvas drawBitmap方法詳解及實(shí)例
這篇文章主要介紹了 Android canvas drawBitmap方法詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-01-01Android中使用GridView實(shí)現(xiàn)仿微信圖片上傳功能(附源代碼)
由于工作要求最近在使用GridView完成圖片的批量上傳功能,我的例子當(dāng)中包含仿微信圖片上傳、拍照、本地選擇、相片裁剪等功能,如果有需要的朋友可以看一下2017-08-08Android 使用URLConnection下載音頻文件的方法
有時(shí)候我們會(huì)需要下載音頻文件。這里提供一種思路,將在線音頻文件通過流寫到本地文件中。需要的朋友可以參考下2019-09-09Android應(yīng)用開發(fā)中模擬按下HOME鍵的效果(實(shí)現(xiàn)代碼)
Android應(yīng)用開發(fā)中, 有一種場(chǎng)景,就是我們不希望用戶直接按Back鍵退出Activity,而是希望應(yīng)用隱藏到后臺(tái),類似于按Home鍵的效果2013-05-05Android Gridview布局出現(xiàn)滾動(dòng)條或組件沖突解決方法
這篇文章主要介紹了Android Gridview布局出現(xiàn)滾動(dòng)條或組件沖突解決方法,GridView是一個(gè)在二維可滾動(dòng)的網(wǎng)格中展示內(nèi)容的控件。網(wǎng)格中的內(nèi)容通過使用adapter自動(dòng)插入到布局中2022-07-07在Android中通過Intent使用Bundle傳遞對(duì)象的使用方法
這篇文章主要介紹了在Android中通過Intent使用Bundle傳遞對(duì)象的使用方法,詳細(xì)介紹Intent使用Bundle傳遞對(duì)象的方法。有需要的可以了解一下。2016-11-11Android 中通過實(shí)現(xiàn)線程更新Progressdialog (對(duì)話進(jìn)度條)
這篇文章主要介紹了Android 中通過實(shí)現(xiàn)線程更新Progressdialog (對(duì)話進(jìn)度條)的相關(guān)資料,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11Android編程中聊天頁面背景圖片、標(biāo)題欄由于鍵盤引起問題的解決方法
這篇文章主要介紹了Android編程中聊天頁面背景圖片、標(biāo)題欄由于鍵盤引起問題的解決方法,針對(duì)鍵盤彈出時(shí)標(biāo)題欄及背景圖片異常的相關(guān)解決方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10