源码问题
昨天遇到一个很奇怪的事情,就是一张图片,在浏览器打开能正常显示,但是在Android上,就死活显示不出来,开始还以为是网站做了防盗链,但是通过代理,却能看到这张图片,通过二进制编码比较,发现glide缓存的文件和gzip压缩后的相同,于是确定是glide不支持gzip压缩后的图片,先尝试解码端解决,发现传过来的数据没法判断是否是gzip压缩后的数据,于是尝试从下载端解决。
网上又有人说okhttp直接支持gzip解压,结果配置为okhttp请求后,发现并没什么卵用,最后找到HttpUrlFether文件,发现里面做了ContentEncoding的初步判断,缺没有处理gzip的类型。
HttpUrlFether中:
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws IOException {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
stream = urlConnection.getInputStream();
}
return stream;
}
源码中判断了ContentEncoding,可是没有对Gzip的类型做处理。
处理方法1
直接修改源码,重新打包:
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws IOException {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
if ("gzip".equalsIgnoreCase(urlConnection.getContentEncoding())){
stream = new GZIPInputStream(urlConnection.getInputStream());
}else {
stream = urlConnection.getInputStream();
}
}
return stream;
}
处理方法2
在不修改源码的基础上,重写HttpUrlGlideUrlLoader和HttpUrlFetcher类,在注册到GlideConfiguration中
public class GzipHttpUrlGlideUrlLoader implements StreamModelLoader<GlideUrl> {
private final ModelCache<GlideUrl, GlideUrl> modelCache;
/**
* The default factory for {@link com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<GlideUrl, GlideUrl>(500);
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new GzipHttpUrlGlideUrlLoader(modelCache);
}
@Override
public void teardown() {
// Do nothing.
}
}
public GzipHttpUrlGlideUrlLoader() {
this(null);
}
public GzipHttpUrlGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
this.modelCache = modelCache;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
return new GzipHttpUrlFetcher(url);
}
}
public class GzipHttpUrlFetcher implements DataFetcher<InputStream> {
private static final String TAG = "HttpUrlFetcher";
private static final int MAXIMUM_REDIRECTS = 5;
private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();
private final GlideUrl glideUrl;
private final HttpUrlConnectionFactory connectionFactory;
private HttpURLConnection urlConnection;
private InputStream stream;
private volatile boolean isCancelled;
public GzipHttpUrlFetcher(GlideUrl glideUrl) {
this(glideUrl, DEFAULT_CONNECTION_FACTORY);
}
// Visible for testing.
GzipHttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.connectionFactory = connectionFactory;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(2500);
urlConnection.setReadTimeout(2500);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
}
}
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws IOException {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
if ("gzip".equalsIgnoreCase(urlConnection.getContentEncoding())){
stream = new GZIPInputStream(urlConnection.getInputStream());
}else {
stream = urlConnection.getInputStream();
}
}
return stream;
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
@Override
public String getId() {
return glideUrl.getCacheKey();
}
@Override
public void cancel() {
// TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
// often called on the main thread.
isCancelled = true;
}
interface HttpUrlConnectionFactory {
HttpURLConnection build(URL url) throws IOException;
}
private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
@Override
public HttpURLConnection build(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
}
@Override
public void registerComponents(Context context, Glide glide) {
// register ModelLoaders here.
glide.register(GlideUrl.class, InputStream.class, new GzipHttpUrlGlideUrlLoader.Factory());
}