做者:林冠宏 / 指尖下的幽靈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
下面兩點
:傳參進來的 ImageView 老是同一個,即 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 引用地址
是同樣的,但圖片像素不同?傳參進來的 ImageView 老是同一個,即 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
的存儲時機。具體見下面的源碼簡析咱們先看看 ViewTarget.java
內部的處理 Request
的方法。
@Override
public void setRequest(Request request) {
setTag(request); // 裏面調用的是 view.setTag 藉助系統的 API 進行存儲
}
@Override
public Request getRequest() {
Object tag = getTag(); // 裏面調用的是 view.setTag
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
}
}
return request;
}
private void setTag(Object tag) {
if (tagId == null) {
isTagUsedAtLeastOnce = true;
view.setTag(tag);
} else {
view.setTag(tagId, tag);
}
}
private Object getTag() {
if (tagId == null) {
return view.getTag();
} else {
return view.getTag(tagId);
}
}
複製代碼
上面已經能夠明顯看出,只要這個 View 傳參入過 glide
,就會被 setTag
,進行 request
的綁定。如今再來看看 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;
}
複製代碼