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

  今天做后台的同事说,为什么你下载一张图片,却对服务器做了两次请求,是不是有冗余的?
我带着这个问题查了一下客户端的代码,图片请求使用了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请求的事情。

你可能感兴趣的:(Android图片处理)