问题描述
最近在开发过程中,QA同学反馈了一个bug:在华为荣耀6(Android 4.4.2)上,有些页面的图片加载不出来,只能展示默认的占位图,效果如下所示:
在项目中,图片展示用的是Fresco
的SimpleDraweeView
组件。第一次看到这个问题时,以为是Fresco
的缓存出了问题,于是首先在手机的应用管理里,找到了对应的APP并清空了缓存。然而,重新启动APP后发现问题依然存在。于是深入分析了一下这个问题,发现了一个值得探讨的技术点,在此记录一下。
问题定位
在清空缓存不解决问题的情况下,接下来做了以下几方面的验证:
图片形状导致不兼容?
难道Fresco
加载圆形图片有兼容性问题?于是又去检查了一下其他页面,发现有些普通的方形图片也显示不出来。
图片的URL有问题?
通过调试,拿到了图片的URL(注:为避免敏感信息,这里连接用的是自己测试的图片,效果都一样):oq54hiwcu.bkt.clouddn.com/2018-10-26-…。把整个图片链接放到浏览器中,发现可以正常打开图片。
如果拿另外一个可以加载成功的图片的URL,通过SimpleDraweeView
的setImageURI(String uriString)
方法,设置给这个显示异常的组件,发现可以正常加载出来!
认真对比了一下两个链接,发现加载失败的链接中除了有中文外,没有其他的差别。把上面图片链接中大发
两个字做URLEncode
之后,得到的链接是:oq54hiwcu.bkt.clouddn.com/2018-10-26-…。当把经过URLEncode
之后的图片链接重新设置给SimpleDraweeView
的时候,发现图片可以正常显示了!
于是问题初步定位:带特殊字符的URL(如中文,空格等),在这款手机上加载不出来!
虽然问题定位到了,但是为什么同样的URL在其他手机(手头有Android 8.0等高版本手机)上可以正常加载图片,在这款手机上就无法加载成功呢?难道Fresco
存在兼容性问题?
问题原因
在项目中,图片的URL
是通过调用SimpleDraweeView
的setImageURI(String uriString)
方法进行设置的。要解决弄明白上面的问题,就需要深入追踪了一下这里源码的实现。
众所周知,Fresco
设计是三级缓存:内存、文件、网络。针对我们当前遇到的问题,初步推断应该是图片在通过网络加载的时候出问题的。
如果在Fresco
初始化时没有自定义网络加载引擎,那Fresco
默认使用的是系统自带的HttpURLConnection
。通过阅读源码可知,Fresco
中通过网络加载图片,最终是通过HttpUrlConnectionNetworkFetcher
类中的downloadFrom(Uri uri, int maxRedirects)
方法来完成网络请求的。源码简化如下:
// HttpUrlConnectionNetworkFetcher.java
private HttpURLConnection downloadFrom(Uri uri, int maxRedirects) throws IOException {
HttpURLConnection connection = openConnectionTo(uri);
connection.setConnectTimeout(mHttpConnectionTimeout);
int responseCode = connection.getResponseCode();
...
}
复制代码
从上面的代码中可以看出,Fresco
默认使用HttpUrlConnection
做网络请求。经过调试发现,带特殊字符的URL在connection.getResponseCode()
执行时,每次返回的responseCode
都是403,即服务器不响应此次请求。当链接中的特殊字符经过URLEncode
之后,responseCode
正常返回200。也就是说这个版本的HttpURLConnection
在底层并不会自动对URL
的Params中的特殊字符做URLEncode
。
解决方案
至此,问题的原因已经清晰明了了,解决方案可以有两种方案:
统一URLEncode
对于项目中所有的图片URL,在调用SimpleDraweeView
的setImageURI(String uriString)
前,统一对参数做一次URLEncode
即可。
需要注意的是:对链接做URLEncode
不能像下面这样直接把整个链接作为参数传入,因为这样会把一些并不需要转换的特殊字符也直接转换掉。
String query = java.net.URLEncoder.encode("pg=q&kl=XX&stype=stext");
// query: pg%3Dq%26kl%3DXX%26stype%3Dstext
复制代码
比如:当我们要对pg=q&kl=XX&stype=stext
的链接做URLEncode
时,如果采用上述方法,最终得到的结果是:pg%3Dq%26kl%3DXX%26stype%3Dstext
,这并不符合我们的预期。因为我们只希望把Params的部分做URLEncode
。这就需要对URL的Params解析