首页 > 代码库 > 从源代码分析Android-Universal-Image-Loader图片下载技巧

从源代码分析Android-Universal-Image-Loader图片下载技巧

在手机上尤其需要考虑网络对图片下载的影响,常见的情况是在2G网络、在3G网络需要不同的下载策略,也就是说在慢速网络与快速网络中下载需要考虑不同的策略。一种常见的策略就是Android客户端和服务端相配合的方式,针对慢速网络对图片进行优化(让图片的质量低一点,保证能下载),但是这种情况不在本文讨论的范围中。在本文中主要讨论针对不能改变的服务器图片质量(图片的大小 xx KB),Android-Universal-Image-Loader所采取的下载策略。

    需要具体考虑网络情况有:快速、慢速、无网络权限。针对这三种情况,在UIL中分别定义了三种策略。还是让我们从代码入手看看。在《从代码分析Android-Universal-Image-Loader的图片加载、显示流程》我们分析了图片的下载是从LoadAndDisplayImageTask.decodeImage(…)中开始的,其中函数内部调用了getDownloader(),然后在ImageDecoder接口的实现类(BaseImageDecoder)中获取InputStream实现图片的下载和解析。跟进去getDownloader()中看看。

private ImageDownloader getDownloader() {        ImageDownloader d;        if (engine.isNetworkDenied()) {            d = networkDeniedDownloader;        } else if (engine.isSlowNetwork()) {            d = slowNetworkDownloader;        } else {            d = downloader;        }        return d;    }

networkDeniedDownloader、slowNetworkDownloader、downloader究竟是什么?在LoadAndDisplayImageTask的构造函数中我们看到他们实际是来源于ImageLoaderConfiguration类中对应的networkDeniedDownloader、slowNetworkDownloader、downloader。在ImageLoaderConfiguration的构造函数总,我们发现downloader来源于ImageLoaderConfiguration.Builder,分析后发现它就是一个BaseImageDownloader对象(最后在DefaultConfigurationFactory.createImageDownloade(…)中被初始化)。回到ImageLoaderConfiguration类的构造函数中(如下所示)

 

private ImageLoaderConfiguration(final Builder builder) {        resources = builder.context.getResources();        maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;        maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;        maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;        maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;        processorForDiskCache = builder.processorForDiskCache;        taskExecutor = builder.taskExecutor;        taskExecutorForCachedImages = builder.taskExecutorForCachedImages;        threadPoolSize = builder.threadPoolSize;        threadPriority = builder.threadPriority;        tasksProcessingType = builder.tasksProcessingType;        diskCache = builder.diskCache;        memoryCache = builder.memoryCache;        defaultDisplayImageOptions = builder.defaultDisplayImageOptions;        downloader 
=
 builder.downloader;        decoder = builder.decoder;        customExecutor = builder.customExecutor;        customExecutorForCachedImages = builder.customExecutorForCachedImages;        networkDeniedDownloader 
= new NetworkDeniedImageDownloader(downloader); slowNetworkDownloader = new
 SlowNetworkImageDownloader(downloader);        L.writeDebugLogs(builder.writeLogs);    }

我们发现networkDeniedDownloader、slowNetworkDownloader都依赖与downloader对象,猜想这两个类应该是对BaseImageDownloader的一个包装。下面我们贴出NetworkDeniedImageDownloader、SlowNetworkImageDownloader的代码(它们在com.nostra13.universalimageloader.core.ImageLoaderConfiguration类中)

/**     * Decorator. Prevents downloads from network (throws {@link IllegalStateException exception}).<br />     * In most cases this downloader shouldn‘t be used directly.     *     * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)     * @since 1.8.0     */    private static class NetworkDeniedImageDownloader implements ImageDownloader {        private final ImageDownloader wrappedDownloader;        public NetworkDeniedImageDownloader(ImageDownloader wrappedDownloader) {            this.wrappedDownloader = wrappedDownloader;        }        @Override        public InputStream getStream(String imageUri, Object extra) throws IOException {            switch (Scheme.ofUri(imageUri)) {                case HTTP:                case HTTPS:                    throw new IllegalStateException();                default:                    return wrappedDownloader.getStream(imageUri, extra);            }        }    }    /**     * Decorator. Handles <a href="http://www.mamicode.com/http://code.google.com/p/android/issues/detail?id=6066">this problem</a> on slow networks     * using {@link com.nostra13.universalimageloader.core.assist.FlushedInputStream}.     *     * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)     * @since 1.8.1     */    private static class SlowNetworkImageDownloader implements ImageDownloader {        private final ImageDownloader wrappedDownloader;        public SlowNetworkImageDownloader(ImageDownloader wrappedDownloader) {            this.wrappedDownloader = wrappedDownloader;        }        @Override        public InputStream getStream(String imageUri, Object extra) throws IOException {            InputStream imageStream = wrappedDownloader.getStream(imageUri, extra);            switch (Scheme.ofUri(imageUri)) {                case HTTP:                case HTTPS:                    return new FlushedInputStream(imageStream);                default:                    return imageStream;            }        }    }

先看到NetworkDeniedImageDownloader类,这个类中由于对应的是没有网络访问权限(android.permission.INTERNET)的情况,这种情况下Http和Https自然就不能使用了,其他情况(如从本地资源中获取图片)还是可以的。NetworkDeniedImageDownloader.wrappedDownloader对象是什么呢?其实就是我们刚刚ImageLoaderConfiguration构造函数中传入的BaseImageDownloader对象。在看看这个类中的getStream(…)方法。

@Override    public InputStream getStream(String imageUri, Object extra) throws IOException {        switch (Scheme.ofUri(imageUri)) {            case HTTP:            case HTTPS:                return getStreamFromNetwork(imageUri, extra);            case FILE:                return getStreamFromFile(imageUri, extra);            case CONTENT:                return getStreamFromContent(imageUri, extra);            case ASSETS:                return getStreamFromAssets(imageUri, extra);            case DRAWABLE:                return getStreamFromDrawable(imageUri, extra);            case UNKNOWN:            default:                return getStreamFromOtherSource(imageUri, extra);        }    }

从这个函数中,我们可以看到UIL通过Scheme.ofUri(…)分析imageUri,根据ImageUri的类型选择对应的方法进行处理。通过分析Scheme类,我们发现UIL支持以下几种图片获取方式HTTP, HTTPS, FILE, CONTENT, ASSETS, DRAWABLE。

接下来,我们分析一下SlowNetworkImageDownloader.getStream(…)方法,每一次图片的下载最终都会通过BitmapFactory.decodeStream解析成Bitmap,供ImageView显示。我们可以发现这个方法针对慢速网络使用FlushedInputStream来处理。使用这个类的原因是因为在慢速网络中,BitmapFactory.decodeStream无法正确解析完整的图片。具体的可以参考StackOverFlow上的帖子《BitmapFactory.decodeStream always returns null and skia decoder shows decode returned false》和一个Google上的Bug 报告《BitmapFactory.decodeStream() fails if InputStream.skip() does not skip fully》。

网速不慢的下载就直接使用BaseImageDownloader.getStream(…)方法了。

至此,我们已经分析了UIL中图片下载技巧,最后梳理一下。为了应对慢速、正常、访问受限网络,UIL分别 使用了SlowNetworkDownloader、BaseImageLoader、NetworkDeniedDownloader来应对这些策略,在LoadAndDisplayImageTask.getDownloader(…)中通过获取对应的downloader,最后通过LoadAndDisplayImageTask.decodeImage(…)将图片解析出来。