作者:林冠宏 / 指尖下的幽灵
掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8
博客:http://www.cnblogs.com/linguanh/
GitHub : https://github.com/af913337456/
腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities
这两天在改造我的私人APP 非ROOT版微信自动回复, 使之可以多开
的时候,碰到一个这样的问题。
Glide 在使用默认的Targer方式下
,同一个 View 加载不同 URL
图片的时候,返回的 Bitmap 引用地址
是一样的,但图片像素不一样。默认的 Target 有 : BitmapImageViewTarget.java,DrawableImageViewTarget.java
默认的方式代码如下:
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() {
@Override
public boolean onException(Exception e, String model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Bitmap resource, String model, Target 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。
上面说的是存每个页面对应的 Bitmap,却没有去存 ImageView
,你可能会问为什么?原因就是为了节省一个 ImageView 的内存
,如果存 ImageView,它自然也携带了当前的 Bitmap 内存,以及它内部的其他变量的内存等。如果单独存 Bitmap,这样在APP中切换页面的时候,其实也就是切换数据,更新数据即可。
结合上面的语言来看,那么上面代码应该是没问题的。而事实上是有问题,因为同时具备了下面两点
:
- 传参进来的 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() {
@Override
public boolean onException(Exception e, String model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
if(resource != null){
LogUitls.e("resource ===> "+resource.toString());
lastTimeQrCodeBitmap = resource;
i.setImageBitmap(lastTimeQrCodeBitmap); // 手动显示
}
return false;
}
}
)
.into(
new SimpleTarget() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation super Bitmap> glideAnimation) {
// 这里的 onResourceReady:resource 和上面的是一样的
}
}
);
这个时候依然传参是同一个 ImageView
也不会
造成 onResourceReady
返回的 resource
内存地址总是同一个的情况。
那么到底是什么原因导致了:
Glide 在满足下面两点的时候,加载返回的 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
其中第4点就是 BitmapPool
的存储时机。具体见下面的源码简析
源码简析:
Glide
的 into
方法,位于 RequestBuilder.java
private > Y into(
@NonNull Y target,
@Nullable RequestListener 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
内存地址不一样的原因。
BitmapPool.get 的时机。
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;
}