Android-Universal-Image-Loader 学习笔记(三)下载器分析

来源:互联网 发布:aws centos root 编辑:程序博客网 时间:2024/06/07 21:52

                                 Android-Universal-Image-Loader 下载器分析

           在手机上尤其需要考虑网络对图片下载的影响,常见的情况是在2G网络、在3G网络需要不同的下载策略,也就是说在慢速网络与快速网络中下载需要考虑不同的策略。
       需要具体考虑网络情况有:快速、慢速、无网络权限。针对这三种情况,在UIL中分别定义了三种策略。
        从代码看:
private ImageDownloader getDownloader() {ImageDownloader d;if (engine.isNetworkDenied()) {// 网络被拒绝 NetworkDeniedImageDownloaderd = networkDeniedDownloader;} else if (engine.isSlowNetwork()) {// 网络很慢 SlowNetworkImageDownloaderd = slowNetworkDownloader;} else {// 一般情况,BaseImageDownloaderd = downloader;}return d;}
     那么networkDeniedDownloader、slowNetworkDownloader、downloader究竟是什么?
      在LoadAndDisplayImageTask的构造函数中我们看到它们实际是来源于ImageLoaderConfiguration类中对应的networkDeniedDownloader、slowNetworkDownloader、downloader。
      在ImageLoaderConfiguration的构造函数中,我们发现downloader来源于ImageLoaderConfiguration.Builder,分析后发现它就是一个BaseImageDownloader对象(最后在DefaultConfigurationFactory.createImageDownloade(…)中被初始化)。
public static ImageDownloader createImageDownloader(Context context) {return new BaseImageDownloader(context);}

我们发现networkDeniedDownloader、slowNetworkDownloader都依赖与downloader对象,猜想这两个类应该是对BaseImageDownloader的一个包装。下面我们贴出NetworkDeniedImageDownloader、SlowNetworkImageDownloader的代码(它们在com.nostra13.universalimageloader.core.ImageLoaderConfiguration类中)
public interface ImageDownloader {/** * Retrieves {@link InputStream} of image by URI. * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws IOException                   if some I/O error occurs during getting image stream * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) */InputStream getStream(String imageUri, Object extra) throws IOException;/** Represents supported schemes(protocols) of URI. Provides convenient methods for work with schemes and URIs. */public enum Scheme {DYNAMICPLUGIN("dynamicplugin"),APPPLUGIN("appplugin"),HEADIMG("headimg"),THUM("thum"),DETAIL("detail"),HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");private String scheme;private String uriPrefix;Scheme(String scheme) {this.scheme = scheme;uriPrefix = scheme + "://";}/** * Defines scheme of incoming URI * * @param uri URI for scheme detection * @return Scheme of incoming URI */public static Scheme ofUri(String uri) {if (uri != null) {for (Scheme s : values()) {if (s.belongsTo(uri)) {return s;}}}return UNKNOWN;}private boolean belongsTo(String uri) {return uri.toLowerCase(Locale.US).startsWith(uriPrefix);}/** Appends scheme to incoming path */public String wrap(String path) {return uriPrefix + path;}/** Removed scheme part ("scheme://") from incoming URI */public String crop(String uri) {if (!belongsTo(uri)) {throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));}return uri.substring(uriPrefix.length());}}}

private static class NetworkDeniedImageDownloader implements ImageDownloader {private final ImageDownloader wrappedDownloader;public NetworkDeniedImageDownloader(ImageDownloader wrappedDownloader) {this.wrappedDownloader = wrappedDownloader;}@Overridepublic 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;}@Overridepublic 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;}}}}
       其中wrappedDownloader就是 BaseImageDownloader,是什么时候传进来的呢?
       看如下代码:
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;                //downloader就是BaseImageDownloadernetworkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);L.writeDebugLogs(builder.writeLogs);}


       
public class BaseImageDownloader implements ImageDownloader {/** {@value} */public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds/** {@value} */public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds/** {@value} */protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb/** {@value} */protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";protected static final int MAX_REDIRECT_COUNT = 5;protected static final String CONTENT_CONTACTS_URI_PREFIX = "content://com.android.contacts/";private static final String ERROR_UNSUPPORTED_SCHEME = "UIL doesn't support scheme(protocol) by default [%s]. " + "You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))";protected final Context context;protected final int connectTimeout;protected final int readTimeout;public BaseImageDownloader(Context context) {this.context = context.getApplicationContext();this.connectTimeout = DEFAULT_HTTP_CONNECT_TIMEOUT;this.readTimeout = DEFAULT_HTTP_READ_TIMEOUT;}public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {this.context = context.getApplicationContext();this.connectTimeout = connectTimeout;this.readTimeout = readTimeout;}@Overridepublic InputStream getStream(String imageUri, Object extra) throws IOException {switch (Scheme.ofUri(imageUri)) {case HTTPS://return getStreamFromNetworkHttps(imageUri, extra);case HTTP: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 HEADIMG:return getStreamFromHeadimg(imageUri, extra);case THUM:return getStreamFromThum(imageUri, extra);case DETAIL:return getStreamFromDetail(imageUri, extra);case DYNAMICPLUGIN:return getStreamFromDynamicPlugin(imageUri, extra);case APPPLUGIN:return getStreamFromAppPlugin(imageUri, extra);case UNKNOWN:default:return getStreamFromOtherSource(imageUri, extra);}}/** * Retrieves {@link InputStream} of image by URI (image is located in the network). * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws IOException if some I/O error occurs during network request or if no InputStream could be created for *                     URL. */protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {HttpURLConnection conn;if(imageUri.startsWith("https")){conn = createHttpsConnection(imageUri, extra);}else{conn = createConnection(imageUri, extra);}int redirectCount = 0;while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {conn = createConnection(conn.getHeaderField("Location"), extra);redirectCount++;}InputStream imageStream;try {imageStream = conn.getInputStream();} catch (IOException e) {// Read all data to allow reuse connection (http://bit.ly/1ad35PY)IoUtils.readAndCloseStream(conn.getErrorStream());throw e;}return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());}///**// * Retrieves {@link InputStream} of image by URI (image is located in the network).// *// * @param imageUri Image URI// * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)// *                 DisplayImageOptions.extraForDownloader(Object)}; can be null// * @return {@link InputStream} of image// * @throws IOException if some I/O error occurs during network request or if no InputStream could be created for// *                     URL.// *///protected InputStream getStreamFromNetworkHttps(String imageUri, Object extra) throws IOException {//////HttpsURLConnection conn = createHttpsConnection(imageUri, extra);////int redirectCount = 0;//while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {//conn = createHttpsConnection(conn.getHeaderField("Location"), extra);//redirectCount++;//}////InputStream imageStream;//try {//imageStream = conn.getInputStream();//} catch (IOException e) {//// Read all data to allow reuse connection (http://bit.ly/1ad35PY)//IoUtils.readAndCloseStream(conn.getErrorStream());//throw e;//}//return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());//}/** * Create {@linkplain HttpURLConnection HTTP connection} for incoming URL * * @param url   URL to connect to * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *              DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@linkplain HttpURLConnection Connection} for incoming URL. Connection isn't established so it still configurable. * @throws IOException if some I/O error occurs during network request or if no InputStream could be created for *                     URL. */protected HttpURLConnection createConnection(String url, Object extra) throws IOException {String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();conn.setConnectTimeout(connectTimeout);conn.setReadTimeout(readTimeout);return conn;}protected HttpsURLConnection createHttpsConnection(String url, Object extra) throws IOException {try {HttpUtil.trustAllHttpsCertificates();} catch (Exception e) {e.printStackTrace();}        HttpsURLConnection.setDefaultHostnameVerifier(HttpUtil.hv);String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);HttpsURLConnection conn = (HttpsURLConnection) new URL(encodedUrl).openConnection();conn.setConnectTimeout(connectTimeout);conn.setReadTimeout(readTimeout);return conn;}/** * Retrieves {@link InputStream} of image by URI (image is located on the local file system or SD card). * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws IOException if some I/O error occurs reading from file system */protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {String filePath = Scheme.FILE.crop(imageUri);return new ContentLengthInputStream(new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE),(int) new File(filePath).length());}/** * Retrieves {@link InputStream} of image by URI (image is accessed using {@link ContentResolver}). * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws FileNotFoundException if the provided URI could not be opened */protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException {ContentResolver res = context.getContentResolver();Uri uri = Uri.parse(imageUri);if (isVideoUri(uri)) { // video thumbnailLong origId = Long.valueOf(uri.getLastPathSegment());Bitmap bitmap = MediaStore.Video.Thumbnails.getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null);if (bitmap != null) {ByteArrayOutputStream bos = new ByteArrayOutputStream();bitmap.compress(CompressFormat.PNG, 0, bos);return new ByteArrayInputStream(bos.toByteArray());}} else if (imageUri.startsWith(CONTENT_CONTACTS_URI_PREFIX)) { // contacts photoreturn ContactsContract.Contacts.openContactPhotoInputStream(res, uri);}return res.openInputStream(uri);}/** * Retrieves {@link InputStream} of image by URI (image is located in assets of application). * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws IOException if some I/O error occurs file reading */protected InputStream getStreamFromAssets(String imageUri, Object extra) throws IOException {String filePath = Scheme.ASSETS.crop(imageUri);return context.getAssets().open(filePath);}/** * Retrieves {@link InputStream} of image by URI (image is located in drawable resources of application). * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image */protected InputStream getStreamFromDrawable(String imageUri, Object extra) {String drawableIdString = Scheme.DRAWABLE.crop(imageUri);int drawableId = Integer.parseInt(drawableIdString);return context.getResources().openRawResource(drawableId);}/** * Retrieves {@link InputStream} of image by URI from other source with unsupported scheme. Should be overriden by * successors to implement image downloading from special sources.<br /> * This method is called only if image URI has unsupported scheme. Throws {@link UnsupportedOperationException} by * default. * * @param imageUri Image URI * @param extra    Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) *                 DisplayImageOptions.extraForDownloader(Object)}; can be null * @return {@link InputStream} of image * @throws IOException                   if some I/O error occurs * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) */protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException {throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, imageUri));}protected InputStream getStreamFromHeadimg(String imageUri, Object extra) throws IOException {String filePath = "headimg/" +Scheme.HEADIMG.crop(imageUri);return context.getAssets().open(filePath);}protected InputStream getStreamFromAppPlugin(String imageUri, Object extra) throws IOException {//  ../images/plugin/mobicon/img_cust4.png 路径是这样子的String[] fArray = Scheme.APPPLUGIN.crop(imageUri).split("\\/");String fileName = fArray[fArray.length-1];String filePath = "apppluginicon/" + fileName;return context.getAssets().open(filePath);}protected InputStream getStreamFromDynamicPlugin(String imageUri, Object extra) throws IOException {//  ../images/plugin/mobicon/img_cust4.png 路径是这样子的String[] fArray = Scheme.DYNAMICPLUGIN.crop(imageUri).split("\\/");String fileName = fArray[fArray.length-1];String filePath = "dynamicpluicon/" + fileName;return context.getAssets().open(filePath);}protected InputStream getStreamFromThum(String imageid, Object extra) throws IOException {//String filePath = "headimg/" +Scheme.HEADIMD.crop(imageUri);String fileName = Scheme.THUM.crop(imageid);String photoPath = PhotoUtils.getThumPhoto(fileName,200);//if(photoPath.startsWith("https")){//return getStreamFromNetworkHttps(photoPath, extra);//}else{return getStreamFromNetwork(photoPath, extra);//}}protected InputStream getStreamFromDetail(String imageUri, Object extra) throws IOException {//String filePath = "headimg/" +Scheme.HEADIMD.crop(imageUri);String fileName = Scheme.DETAIL.crop(imageUri);String photoPath = PhotoUtils.getDetailPhoto(fileName);//if(photoPath.startsWith("https")){//return getStreamFromNetworkHttps(photoPath, extra);//}else{return getStreamFromNetwork(photoPath, extra);//}}private boolean isVideoUri(Uri uri) {String mimeType = context.getContentResolver().getType(uri);if (mimeType == null) {return false;}return mimeType.startsWith("video/");}}

从这个函数中,我们可以看到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(…)将图片解析出来。
         记录到此。
0 0
原创粉丝点击