做者:林冠宏 / 指尖下的幽灵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; }