从源代码分析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://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(…)将图片解析出来。