final修饰的Boolean(布尔值)可以被修改值?

2,024 阅读2分钟

好记性不如烂笔头。生活中多做笔记,不仅可以方便自己,还可以方便他人。

背景

前几天,测试同学提了个跟头像有关的bug,我去检查代码,梳理逻辑,打log,调试代码。头像的显示采用的是Glide库的组件。大概的代码逻辑如下:

    private void setImage(ImageView imageView, String url, final boolean test) {
        Log.d(TAG, "setImage, test = " + test);
        Glide.with(this)
                .asBitmap()
                .load(url)
                .into(new ImageViewTarget<Bitmap>(imageView) {
                    @Override
                    protected void setResource(@Nullable Bitmap resource) {

                    }

                    @Override
                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                        super.onResourceReady(resource, transition);
                        if (test) {
                            Log.d(TAG, "onResourceReady, test = " + test);
                        }
                    }
                });
    }

问题

在不同的地方,调用了上面的方法(setImage)多次,打log,其中出现这样的一组情况:

    ... setImage, test = true
    ... onResourceReady, test = false

test变量为final类型,理论上打出来的log,要么都是true,要么都是false,不应该出现一个true一个false的啊。难道final修饰的boolean值也可以被修改?

原因

不用怀疑,被final修饰的boolean值是不可以被修改的。问题出现在glide。查看了glide源码的into方法(glide版本是4.5):

    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();
      // If the request is completed, beginning again will ensure the result is re-delivered,
      // triggering RequestListeners and Targets. If the request is failed, beginning again will
      // restart the request, giving it another chance to complete. If the request is already
      // running, we can let it continue running without interruption.
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        // Use the previous request rather than the new one to allow for optimizations like skipping
        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
        // that are done in the individual Request.
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

仔细看源码,就会发现这个地方:

    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }
    @Override
  public boolean isEquivalentTo(Request o) {
    if (o instanceof SingleRequest) {
      SingleRequest<?> that = (SingleRequest<?>) o;
      return overrideWidth == that.overrideWidth
          && overrideHeight == that.overrideHeight
          && Util.bothModelsNullEquivalentOrEquals(model, that.model)
          && transcodeClass.equals(that.transcodeClass)
          && requestOptions.equals(that.requestOptions)
          && priority == that.priority
          // We do not want to require that RequestListeners implement equals/hashcode, so we don't
          // compare them using equals(). We can however, at least assert that the request listener
          // is either present or not present in both requests.
          && (requestListener != null
          ? that.requestListener != null : that.requestListener == null);
    }
    return false;
  }

如果当前request的配置跟之前的previous配置是一样的,而且没有设置跳过缓存(isSkipMemoryCacheWithCompletePreviousRequest),就会直接拿之前的previous去请求,当前构造的request就会回收了。问题就出在这里

    private void setImage(ImageView imageView, String url, final boolean test) {
        Log.d(TAG, "setImage, test = " + test);
        Glide.with(this)
                .asBitmap()
                .load(url)
                .into(new ImageViewTarget<Bitmap>(imageView) {
                    @Override
                    protected void setResource(@Nullable Bitmap resource) {

                    }

                    @Override
                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                        super.onResourceReady(resource, transition);
                        if (test) {
                            Log.d(TAG, "onResourceReady, test = " + test);
                        }
                    }
                });
    }

所以,上面的ImageViewTarget并不是新构建的target,而是以前旧的那个,所以上面的test变量的值并不是新传入的,而是之前传入的。OK,问题解决了,这是glide的一个坑。

总结

虽然是个简单问题,但是也告诉我一个道理,遇到问题,多看源码,多分析源码。