关于Glide设置请求头之后图片加载闪烁的问题研究(即使用请求头之后glide缓存失效问题)
在平常的开发中,经常使用Glide作为第三方图片加载框架。Glide作为一个很成熟的框架基本满足了各种条件下的图片加载和缓存需求。一般情况下我们只需要将图片地址的url作为参数传入,Glide就会自动将请求完成并进行缓存,并加载到相对应的图片控件中。但是在一次开发中,笔者用Glide做列表项头像的加载时发现,每次刷新列表的时候,列表的头像都会闪烁,使用效果很不理想。对此问题,做这篇文章做一下记录。
那么问题出在哪里呢,我试着用随便百度的网络图片地址加入列表中,发现并没有出现这个问题,通过对比发现,由于我们自己的服务器加入了token校验,所以在请求的时候,需要设置Glide的请求头,熟悉Glide同学们都是知道的,Glide的请求头是通过GlideUrl添加的,GlideUrl的源码其实很简单,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155package 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; } }
使用也很简单:
1
2
3
4
5
6
7new GlideUrl("地址", new Headers() { @Override public Map<String, String> getHeaders() { return null; } });
这个的getHeaders()方法把请求头作为返回值返回给Glide就可以了。
但是,这就会出现一个问题,就是Glide的缓存机制失效了,直接导致了每次刷新列表的时候都会闪烁,因为每次其实Glide都没使用缓存而是直接去请求了一次新的数据,那么加载的时候就会有延迟,闪烁也就成了必然。
那么为什么Glide的缓存失效了呢,我们看源码,问题就出在这:
Glide是通过这两个函数作为对比GlideUrl是否相同并作为去缓存的依据的,那么显然其实我们每次去添加Header的时候,这两个的返回值必然是不一样的,因为其实简单来说,headers参数的内存地址是不一样的,那么必然会导致这两个方法出问题。
那么问题就很简单明了,方案有很多种,简单的来说,就是让这两个方法在其实是同一个图片地址的时候,给出同样的返回值就OK了
要么就是重写上图这个匿名内部类的equals和hashCode方法,要么就重写整个GlideUrl的这俩方法,因为笔者的业务关系,所有我用的是第二种方法。具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74import 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; } }
其实就是从比内存改成了比内容,这样再试一次,发现列表刷新的时候没有问题了。
最后
以上就是傻傻篮球最近收集整理的关于关于Glide设置请求头之后图片加载闪烁的问题研究(即使用请求头之后glide缓存失效问题)的全部内容,更多相关关于Glide设置请求头之后图片加载闪烁内容请搜索靠谱客的其他文章。
发表评论 取消回复