关于Glide设置请求头之后图片加载闪烁的问题研究(即使用请求头之后glide缓存失效问题)

关于Glide设置请求头之后图片加载闪烁的问题研究(即使用请求头之后glide缓存失效问题)

在平常的开发中,经常使用Glide作为第三方图片加载框架。Glide作为一个很成熟的框架基本满足了各种条件下的图片加载和缓存需求。一般情况下我们只需要将图片地址的url作为参数传入,Glide就会自动将请求完成并进行缓存,并加载到相对应的图片控件中。但是在一次开发中,笔者用Glide做列表项头像的加载时发现,每次刷新列表的时候,列表的头像都会闪烁,使用效果很不理想。对此问题,做这篇文章做一下记录。

那么问题出在哪里呢,我试着用随便百度的网络图片地址加入列表中,发现并没有出现这个问题,通过对比发现,由于我们自己的服务器加入了token校验,所以在请求的时候,需要设置Glide的请求头,熟悉Glide同学们都是知道的,Glide的请求头是通过GlideUrl添加的,GlideUrl的源码其实很简单,如下:

package com.bumptech.glide.load.model;

import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.util.Preconditions;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Map;

/**
 * A wrapper for strings representing http/https URLs responsible for ensuring URLs are properly
 * escaped and avoiding unnecessary URL instantiations for loaders that require only string urls
 * rather than URL objects.
 *
 * <p>  Users wishing to replace the class for handling URLs must register a factory using
 * GlideUrl. </p>
 *
 * <p> To obtain a properly escaped URL, call {@link #toURL()}. To obtain a properly escaped string
 * URL, call {@link #toStringUrl()}. To obtain a less safe, but less expensive to calculate cache
 * key, call {@link #getCacheKey()}. </p>
 *
 * <p> This class can also optionally wrap {@link com.bumptech.glide.load.model.Headers} for
 * convenience. </p>
 */
public class GlideUrl implements Key {
  private static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%;$";
  private final Headers headers;
  @Nullable private final URL url;
  @Nullable private final String stringUrl;

  @Nullable private String safeStringUrl;
  @Nullable private URL safeUrl;
  @Nullable private volatile byte[] cacheKeyBytes;

  private int hashCode;

  public GlideUrl(URL url) {
    this(url, Headers.DEFAULT);
  }

  public GlideUrl(String url) {
    this(url, Headers.DEFAULT);
  }

  public GlideUrl(URL url, Headers headers) {
    this.url = Preconditions.checkNotNull(url);
    stringUrl = null;
    this.headers = Preconditions.checkNotNull(headers);
  }

  public GlideUrl(String url, Headers headers) {
    this.url = null;
    this.stringUrl = Preconditions.checkNotEmpty(url);
    this.headers = Preconditions.checkNotNull(headers);
  }

  public URL toURL() throws MalformedURLException {
    return getSafeUrl();
  }

  // See http://stackoverflow.com/questions/3286067/url-encoding-in-android. Although the answer
  // using URI would work, using it would require both decoding and encoding each string which is
  // more complicated, slower and generates more objects than the solution below. See also issue
  // #133.
  private URL getSafeUrl() throws MalformedURLException {
    if (safeUrl == null) {
      safeUrl = new URL(getSafeStringUrl());
    }
    return safeUrl;
  }

  /**
   * Returns a properly escaped {@link String} url that can be used to make http/https requests.
   *
   * @see #toURL()
   * @see #getCacheKey()
   */
  public String toStringUrl() {
    return getSafeStringUrl();
  }

  private String getSafeStringUrl() {
    if (TextUtils.isEmpty(safeStringUrl)) {
      String unsafeStringUrl = stringUrl;
      if (TextUtils.isEmpty(unsafeStringUrl)) {
        unsafeStringUrl = Preconditions.checkNotNull(url).toString();
      }
      safeStringUrl = Uri.encode(unsafeStringUrl, ALLOWED_URI_CHARS);
    }
    return safeStringUrl;
  }

  /**
   * Returns a non-null {@link Map} containing headers.
   */
  public Map<String, String> getHeaders() {
    return headers.getHeaders();
  }

  /**
   * Returns an inexpensive to calculate {@link String} suitable for use as a disk cache key.
   *
   * <p>This method does not include headers.
   *
   * <p>Unlike {@link #toStringUrl()}} and {@link #toURL()}, this method does not escape
   * input.
   */
  // Public API.
  @SuppressWarnings("WeakerAccess")
  public String getCacheKey() {
    return stringUrl != null ? stringUrl : Preconditions.checkNotNull(url).toString();
  }

  @Override
  public String toString() {
    return getCacheKey();
  }

  @Override
  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
    messageDigest.update(getCacheKeyBytes());
  }

  private byte[] getCacheKeyBytes() {
    if (cacheKeyBytes == null) {
      cacheKeyBytes = getCacheKey().getBytes(CHARSET);
    }
    return cacheKeyBytes;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof GlideUrl) {
      GlideUrl other = (GlideUrl) o;
      return getCacheKey().equals(other.getCacheKey())
          && headers.equals(other.headers);
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashCode == 0) {
      hashCode = getCacheKey().hashCode();
      hashCode = 31 * hashCode + headers.hashCode();
    }
    return hashCode;
  }
}

使用也很简单:

new GlideUrl("地址", new Headers() {
                @Override
                public Map<String, String> getHeaders() {
                    return null;
                }
            });

这个的getHeaders()方法把请求头作为返回值返回给Glide就可以了。
但是,这就会出现一个问题,就是Glide的缓存机制失效了,直接导致了每次刷新列表的时候都会闪烁,因为每次其实Glide都没使用缓存而是直接去请求了一次新的数据,那么加载的时候就会有延迟,闪烁也就成了必然。

那么为什么Glide的缓存失效了呢,我们看源码,问题就出在这:
在这里插入图片描述
Glide是通过这两个函数作为对比GlideUrl是否相同并作为去缓存的依据的,那么显然其实我们每次去添加Header的时候,这两个的返回值必然是不一样的,因为其实简单来说,headers参数的内存地址是不一样的,那么必然会导致这两个方法出问题。

那么问题就很简单明了,方案有很多种,简单的来说,就是让这两个方法在其实是同一个图片地址的时候,给出同样的返回值就OK了

在这里插入图片描述
要么就是重写上图这个匿名内部类的equals和hashCode方法,要么就重写整个GlideUrl的这俩方法,因为笔者的业务关系,所有我用的是第二种方法。具体代码如下

import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;

import java.net.URL;
import java.util.Map;


public class IMTokenGlideUrl extends GlideUrl {

    private int mHashCode;

    public IMTokenGlideUrl(URL url) {
        super(url);
    }

    public IMTokenGlideUrl(String url) {
        super(url);
    }

    public IMTokenGlideUrl(URL url, Headers headers) {
        super(url, headers);
    }

    public IMTokenGlideUrl(String url, Headers headers) {
        super(url, headers);
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof GlideUrl) {
            GlideUrl other = (GlideUrl) o;
            return getCacheKey().equals(other.getCacheKey())
                    && !mapCompare(getHeaders(), other.getHeaders());

        }
        return false;

    }

    @Override
    public int hashCode() {
        if (mHashCode == 0) {
            mHashCode = getCacheKey().hashCode();
            if (getHeaders() != null) {
                for (String s : getHeaders().keySet()) {
                    if (getHeaders().get(s) != null) {
                        mHashCode = 31 * mHashCode + getHeaders().get(s).hashCode();
                    }
                }
            }
        }
        return mHashCode;
    }

    private static boolean mapCompare(Map<String, String> map1, Map<String, String> map2) {
        boolean differ = false;
        if (map1 != null && map2 != null) {
            if (map1.size() == map2.size()) {
                for (Map.Entry<String, String> entry1 : map1.entrySet()) {
                    String value1 = entry1.getValue() == null ? "" : entry1.getValue();
                    String value2 = map2.get(entry1.getKey()) == null ? "" : map2.get(entry1.getKey());
                    if (!value1.equals(value2)) {
                        differ = true;
                        break;
                    }
                }
            }
        } else differ = map1 != null || map2 != null;

        return differ;
    }
}

其实就是从比内存改成了比内容,这样再试一次,发现列表刷新的时候没有问题了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值