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