universal image loader 问题一:加载一张图片两次请求服务器

时间:2022-09-13 20:48:39

  今天做后台的同事说,为什么你下载一张图片,却对服务器做了两次请求,是不是有冗余的?
我带着这个问题查了一下客户端的代码,图片请求使用了UIL这个框架,于是下载了源码跟踪调试。
定位到了下载图片任务这个类LoadAndDisplayImageTask.java

private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;

checkTaskNotActual();
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;

//String imageUriForDecoding = uri;
//检查是否使用的磁盘缓存,如果使用的话会先将图片下载下来,然后保存到磁盘一个指定目录。
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
//如果已经保存到磁盘,下面就更新一下图片的uri指向本地目录对应图片的uri。
imageUriForDecoding = cheme.FILE.wrap(imageFile.getAbsolutePath());
}
}

checkTaskNotActual();
//如果没有保存到磁盘缓存,下面解析图片对应的uri还是从服务器上取到的,否则,这个
//时候取到的uri就是已经存储到磁盘上的本地图片的uri
bitmap = decodeImage(imageUriForDecoding);

if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}

  到了这里可能还看不出来是什么原因。那么继续跟踪bitmap = decodeImage(imageUriForDecoding);
定位到了在BaseImageDecoder.java类中

    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
//第一次发送http请求,获取图片下载的输入流
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
//跟踪resetStream方法可以看到,这里面做了第二次发送http请求,获取图片下载的输入流。
imageStream = resetStream(imageStream, decodingInfo);
//预处理图片,得到图片大小,计算一个inSampleSize。
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
//最终在这个地方来解析图片的输入流,生成一个bitmap对象,然后返回给指定回调来使用。
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
Log.d("TEST","getImageStream ="+decodedBitmap.getWidth()+" h = "+decodedBitmap.getHeight()+"imageInfo.imageSize="+imageInfo.imageSize);
} finally {
IoUtils.closeSilently(imageStream);
}

if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}

  好了,找到了冗余请求的位置了。那么为什么会这么做呢?
  Android系统对bitmap解析有一个预处理的优化选项,就是为了避免OOM,提高图片解析效率,可以使用inJustDecodeBounds属性,只加载图片信息,不将图片本身加载到内存。
  
Android API是这么描述:
API这样说:如果该 值设为true那么将不返回实际的bitmap,也不给其分配内存空间这样就避免内存溢出了。但是允许我们查询图片的信息这其中就包括图片大小信息(options.outHeight (图片原始高度)和option.outWidth(图片原始宽度))。

  因此,我们可以在第一次解析图片输入流的时候得到图片的大小等信息,第二次解析图片输入流的时候再根据应用实际需要、设备配置等,计算一个适合的inSampleSize(缩放值)来获取最合适的图片bitmap对象。回到代码,可以看到作者就是这个思路。
  那么下载一张图两次http请求会不会产生流量的浪费呢?从服务器监听的数据看到,实际上第一次请求返回的数据量是比较小的,第二次请求才返回完整的图片大小的数据量。所以这里看到流量浪费是有的,但是并不是两次都请求得到了完整的图片大小的数据,具体为什么,后面需要对http请求输入流的处理研究一下才能解释清楚。
  那么怎么解决这个问题呢,从上面的代码分析可以看到,只要在使用UIL初始化的时候加上磁盘缓存这个选项就可以了。因为加上了磁盘缓存,在decode图片uri的时候实际上解析的是已经下载到本地缓存目录的图片的uri,所以不会出现对服务器发送两次http请求的事情。