做者:林冠宏 / 指尖下的幽靈java
掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8git
博客:http://www.cnblogs.com/linguanh/github
GitHub : https://github.com/af913337456/緩存
騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities微信
這兩天在改造個人私人APP 非ROOT版微信自動回覆, 使之能夠多開
的時候,碰到一個這樣的問題。app
Glide 在使用默認的Targer方式下
,同一個 View 加載不一樣 URL
圖片的時候,返回的 Bitmap 引用地址
是同樣的,但圖片像素不同。默認的 Target 有 : BitmapImageViewTarget.java,DrawableImageViewTarget.javaide
默認的方式代碼以下:函數
private Bitmap lastTimeQrCodeBitmap; private void showQrCodeImage(final ImageView i){ if(wechatCoreApi == null) return; Glide.with(context) .load("xxxxxxxxxxxxxxxxxxx") .asBitmap() .override(400,400) .skipMemoryCache(true) .listener( new RequestListener<String, Bitmap>() { @Override public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) { if(resource != null){ // 這裏打印出加載回來的 Bitmap 的內存地址 LogUitls.e("resource ===> "+resource.toString()); lastTimeQrCodeBitmap = resource; } return false; } } ) .into(i); }
很普通的一個函數,沒過多的操做,僅僅是在 onResourceReady
處作了加載回來的 Bitmap
的保存工做。之所要保存它,是由於這個APP要實現多開
,每個頁面其對應的有一個二維碼圖片
,每個二維碼圖片的 bitmap 是不一樣的,這樣在切換的時候,就能夠對應顯示出屬於當前頁面的 bitmap。post
上面說的是存每一個頁面對應的 Bitmap,卻沒有去存 ImageView
,你可能會問爲何?緣由就是爲了節省一個 ImageView 的內存
,若是存 ImageView,它天然也攜帶了當前的 Bitmap 內存,以及它內部的其餘變量的內存等。若是單獨存 Bitmap,這樣在APP中切換頁面的時候,其實也就是切換數據,更新數據便可。ui
下面兩點
:into(ImageView)
,ImageView
老是同一個使用了默認的 into(ImageView)
函數,這個內部默認使用了BitmapImageViewTarget
:
BitmapImageViewTarget extends ImageViewTarget extends ViewTarget extends BaseTarget
這兩點就致使了,在 onResourceReady
返回的 resource
內存地址老是同一個。簡單修改如下,打破上面兩點任一一點,就能驗證,例以下面的代碼,咱們不採用繼承於 ViewTarger
的 Target
。而使用 SimpleTarget extends BaseTarget
Glide.with(context) .load("xxxxxx") .asBitmap() .override(400,400) .skipMemoryCache(true) .listener( new RequestListener<String, Bitmap>() { @Override public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) { if(resource != null){ LogUitls.e("resource ===> "+resource.toString()); lastTimeQrCodeBitmap = resource; i.setImageBitmap(lastTimeQrCodeBitmap); // 手動顯示 } return false; } } ) .into( new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { // 這裏的 onResourceReady:resource 和上面的是同樣的 } } );
這個時候依然傳參是同一個 ImageView
也不會
形成 onResourceReady
返回的 resource
內存地址老是同一個的狀況。
Bitmap 引用地址
是同樣的,但圖片像素不同?into(ImageView)
,ImageView
老是同一個使用了默認的 into(ImageView)
函數,這個內部默認使用了BitmapImageViewTarget
:
BitmapImageViewTarget extends ImageViewTarget extends ViewTarget extends BaseTarget
分析源碼
和 調試源碼找出調用鏈
獲得以下的答案。我先給出結論,下面再作基於 Glide 4.0
的源碼簡析。
ViewTarget
內部使用 View.setTag
作了 Request
的緩存保存。致使同一個 View
屢次傳入 into(...)
方法的時候,總能找到上一次請求的 Request
。Request
是 Glide
源碼裏面的一個接口,這裏的緩存保存是保存的都是它的實現類。
glide
默認的加載形式中 Target
都繼承了 ViewTarget
SimpleTarget
沒有繼承 ViewTarget
glide
在每次請求開始的時候會去調用 target.getRequest()
,若是獲取的 request
不爲 null
,那麼它就會去釋放上一個請求的一些資源,最後會調用到 BitmapPool.put(Bitmap)
把上一次的 Bitmap
緩存起來。若是 request
獲取的是 null,那麼就不會緩存上一次加載成功的 Bitmap
。最後在加載圖片並解碼完成後,在從 BitmapPool
中尋找緩存的時候,就能找到上面的緩存的,擦除像素,加入新圖片的像素,最終返回 Bitmap
BitmapPool
的存儲時機。具體見下面的源碼簡析Glide
的 into
方法,位於 RequestBuilder.java
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(); previous.begin(); } return target; } requestManager.clear(target); // 進入這裏 target.setRequest(request); requestManager.track(target, request); return target; }
進入 requestManager.clear(target);
裏面。位於 RequestManager.java
public void clear(@Nullable final Target<?> target) { if (target == null) { return; } if (Util.isOnMainThread()) { untrackOrDelegate(target); // 進入這裏 --- ① } else { mainHandler.post(new Runnable() { @Override public void run() { clear(target); // 若是是子線程調用 glide,那麼最終 post 了這個 msg 也是進入到上面 ① 處 } }); } } private void untrackOrDelegate(@NonNull Target<?> target) { boolean isOwnedByUs = untrack(target); // 進入這裏 if (!isOwnedByUs && !glide.removeFromManagers(target) && target.getRequest() != null) { Request request = target.getRequest(); target.setRequest(null); request.clear(); } } boolean untrack(@NonNull Target<?> target) { Request request = target.getRequest(); if (request == null) { // 對應結論中的第一點,若是是同一個 View,那麼它不爲 null return true; } if (requestTracker.clearRemoveAndRecycle(request)) { // 不爲 null,進入這裏的判斷 targetTracker.untrack(target); target.setRequest(null); return true; } else { return false; } }
進入到 clearRemoveAndRecycle
,位於 RequestTracker.java
public boolean clearRemoveAndRecycle(@Nullable Request request) { return clearRemoveAndMaybeRecycle(request, /*isSafeToRecycle=*/ true); } private boolean clearRemoveAndMaybeRecycle(@Nullable Request request, boolean isSafeToRecycle) { if (request == null) { return true; } boolean isOwnedByUs = requests.remove(request); // 這裏的 remove 是會返回 true 的,由於這個 request 不是 null isOwnedByUs = pendingRequests.remove(request) || isOwnedByUs; if (isOwnedByUs) { request.clear(); // 最後進入這裏,這裏的 Request 的實現類是 SingleRequest if (isSafeToRecycle) { request.recycle(); } } return isOwnedByUs; }
進入 SingleRequest.java
的 clear()
@Override public void clear() { Util.assertMainThread(); assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); if (status == Status.CLEARED) { return; } cancel(); if (resource != null) { releaseResource(resource); // 進入這裏 } if (canNotifyCleared()) { target.onLoadCleared(getPlaceholderDrawable()); } status = Status.CLEARED; } private void releaseResource(Resource<?> resource) { engine.release(resource); this.resource = null; }
以後的流程還不少步,至關之複雜。它們最終會走到 BitmapResource.java
裏面的
@Override public void recycle() { bitmapPool.put(bitmap); // 這裏就把上一次加載返回過的 bitmap 給緩存起來了。 }
當咱們不使用 ViewTarget
的 Target
的時候,就不會有上面的流程,由於 BaseTarget.java
內部的 getRequest
是 null,而 SimpleTarget extends BaseTarget
,這也是爲何 SimpleTarget.java
可以達到每次請求返回的 Bitmap
內存地址不同的緣由。
Glide
加載圖片最後的解碼代碼在 Downsampler.java
裏面。它在裏面調用了 decodeFromWrappedStreams
,並在 decodeStream
以前,調用了 setInBitmap
,而 setInBitmap
內部就有這麼一行:
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
它從 bitmapPool
獲取擦除了像素
的 Bitmap 對象。
private Bitmap decodeFromWrappedStreams(InputStream is, BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, boolean fixBitmapToRequestedDimensions, DecodeCallbacks callbacks) throws IOException { .... if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) { .... if (expectedWidth > 0 && expectedHeight > 0) { // setInBitmap setInBitmap(options, bitmapPool, expectedWidth, expectedHeight); } } Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); ... return rotated; }