专栏首页小满Universal-Image-Loader源码解解析---display过程 + 获取bitmap过程

Universal-Image-Loader源码解解析---display过程 + 获取bitmap过程

Universal-Image-Loader在github上的地址:https://github.com/nostra13/Android-Universal-Image-Loader

它的基本使用请参考我的另一篇博客 http://www.cnblogs.com/yuan1225/p/8426900.html,下面我从源码角度研究它。

ImageLoader使用的是双重判断的懒汉试单例模式。

 1 /** Returns singleton class instance */
 2     public static ImageLoader getInstance() {
 3         if (instance == null) {
 4             synchronized (ImageLoader.class) {
 5                 if (instance == null) {
 6                     instance = new ImageLoader();
 7                 }
 8             }
 9         }
10         return instance;
11     }

先看ImageLoader的初始化过程:

 1 /**
 2      * Initializes ImageLoader instance with configuration.<br />
 3      * If configurations was set before ( {@link #isInited()} == true) then this method does nothing.<br />
 4      * To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first.
 5      *
 6      * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration}
 7      * @throws IllegalArgumentException if <b>configuration</b> parameter is null
 8      */
 9     public synchronized void init(ImageLoaderConfiguration configuration) {
10         if (configuration == null) {
11             throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
12         }
13         if (this.configuration == null) {
14             L.d(LOG_INIT_CONFIG);
15             engine = new ImageLoaderEngine(configuration);
16             this.configuration = configuration;
17         } else {
18             L.w(WARNING_RE_INIT_CONFIG);
19         }
20     }

以上是在Application中调用 ImageLoader.getInstance().init(config.build());的初始化过程。接下来是ImageLoader的使用过程分析

ImageLoader.getInstance().displayImage(url, imageView, options);
displayImage方法有很大重载的方法,最终都会辗转调用到最复杂的这个重载方法:
 1 public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
 2             ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
 3         checkConfiguration();
 4         if (imageAware == null) {
 5             throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
 6         }
 7         if (listener == null) {
 8             listener = defaultListener;
 9         }
10         if (options == null) {
11             options = configuration.defaultDisplayImageOptions;
12         }
13 
14         if (TextUtils.isEmpty(uri)) {
15             engine.cancelDisplayTaskFor(imageAware);
16             listener.onLoadingStarted(uri, imageAware.getWrappedView());
17             if (options.shouldShowImageForEmptyUri()) {
18                 imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
19             } else {
20                 imageAware.setImageDrawable(null);
21             }
22             listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
23             return;
24         }
25 
26         if (targetSize == null) {
27             targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
28         }
29         String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
30         engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
31 
32         listener.onLoadingStarted(uri, imageAware.getWrappedView());
33 
34         Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
35         if (bmp != null && !bmp.isRecycled()) {
36             L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
37 
38             if (options.shouldPostProcess()) {
39                 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
40                         options, listener, progressListener, engine.getLockForUri(uri));
41                 ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
42                         defineHandler(options));
43                 if (options.isSyncLoading()) {
44                     displayTask.run();
45                 } else {
46                     engine.submit(displayTask);
47                 }
48             } else {
49                 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
50                 listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
51             }
52         } else {
53             if (options.shouldShowImageOnLoading()) {
54                 imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
55             } else if (options.isResetViewBeforeLoading()) {
56                 imageAware.setImageDrawable(null);
57             }
58 
59             ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
60                     options, listener, progressListener, engine.getLockForUri(uri));
61             LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
62                     defineHandler(options));
63             if (options.isSyncLoading()) {
64                 displayTask.run();
65             } else {
66                 engine.submit(displayTask);
67             }
68         }
69     }

参数的意义:

1.URI uri : Image URI;可用的几种URI:

"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
2.ImageAware imageAware : 显示图像的视图,是androidimageview的包装类,参数来源是new ImageViewAware(imageView).不能为空
3.DisplayImageOptions options :用于图像解码和显示,如果该参数为空则使用默认的option。
4.ImageSize targetSize:图像目标大小。 如果为空 - 大小将取决于视图。
5.ImageLoadingListener listener:用于图像加载过程监听。 如果在UI线程上调用此方法,则监听器在UI线程上触发事件
6.ImageLoadingProgressListener progressListener:图像加载进度监听。 如果在UI线程上调用此方法,则监听器在UI线程上触发事件。 应在{option选项}中启用缓存磁盘以使此侦听器正常工作。
这个方法比较长,它的逻辑比较清晰,主要做了下面的方法:
1.判断各个参数是否合法,是否需要默认值
     判断配置参数和显示图片的控件是否为空,如果为空直接抛出了异常
     判断listener options targetsize是否为null,如果为空则使用默认值
     判断uri是否为空,如果uri为空,则在ImageLoaderEngine中取消该视图的显示任务,如果在options中设置了showImageForEmptyUri(R.drawable.ic_empty)则为该视图显示一个默认的空uri时的图片,直接返回。
2.开始监听下载任务。先从缓存中读取图片的bitmap,如果缓存中有则直接使用,否则需要从磁盘或者从网络下载图片。
下面就来看如何从缓存中读取,如何下载。
当从memoryCache读取的bitmap不为null 并且没有被回收时,就直接展示缓存中的这个bitmap。默认情况下options.shouldPostProcess()是false。除非在初始化options选项时设置了postProcesser。
所以我们之间看49行。点开display方法,咦,它是一个接口。
它有几个实现类分别实现不同的图片显示方法。如果在初始化options选项没有设置displayer()选项则默认使用SimpleBitmapDisplayer()正常显示一张图片。如果设置了如下
.displayer(new CircleBitmapDisplayer(Color.WHITE,5))则显示圆角图片。

下面以SimpleBitmapDisplayer为例,分析如何实现display的。

1 public final class SimpleBitmapDisplayer implements BitmapDisplayer {
2     @Override
3     public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
4         imageAware.setImageBitmap(bitmap);
5     }
6 }

这个实现类比较简单,只有一个方法一句话,可以把它理解成我们在android中给imageView设置图片的一种方式:imageview.setImageBitmap(bitmap);

ImageViewAware是Android imageView的包装类,保持ImageView的弱引用以防止内存泄漏。如何使用imageview的弱引用这一步暂时忽略,先回到第34行。

以上是缓存中有bitmap,下面分析如果从缓存中获取的bitmap为空,则需要加载。因为Androidx.x之后不容许在UI线程中做网络加载的操作,所以我们只分析异步加载的方式,就是第66行engine.submit(displayTask);

在这里涉及到了一个非常重要的类:ImageLoaderEngine,它负责{LoadAndDisplayImageTask显示任务}的执行。下面重点解析一下ImageLoaderEngine这个类。
engine这个对象是什么时候初始化的呢?请回到Imageloader对象的初始化方法init中,第15行:engine = new ImageLoaderEngine(configuration);
看一下ImageLoaderEngine的构造方法:
1 ImageLoaderEngine(ImageLoaderConfiguration configuration) {
2         this.configuration = configuration;
3 
4         taskExecutor = configuration.taskExecutor;
5         taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
6 
7         taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
8     }

以taskExecutorForCachedImages 为例,taskExecutorForCachedImages 是一个线程池,异步显示memory cache里面的bitmap。

进入submit方法:

 1     /** Submits task to execution pool */
 2     void submit(final LoadAndDisplayImageTask task) {
 3         taskDistributor.execute(new Runnable() {
 4             @Override
 5             public void run() {
 6                 File image = configuration.diskCache.get(task.getLoadingUri());
 7                 boolean isImageCachedOnDisk = image != null && image.exists();
 8                 initExecutorsIfNeed();
 9                 if (isImageCachedOnDisk) {
10                     taskExecutorForCachedImages.execute(task);
11                 } else {
12                     taskExecutor.execute(task);
13                 }
14             }
15          });
16     }

当执行到第10行时,就会调用LoadAndDisplayImageTask的run方法,接下来看这个类。这个类的对象封装了engine和Imageloadinginfo,所以包含了所有的configuration 和options选项。

来看它的run方法

 1 @Override
 2     public void run() {
 3         if (waitIfPaused()) return;
 4         if (delayIfNeed()) return;
 5 
 6         ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
 7         L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
 8         if (loadFromUriLock.isLocked()) {
 9             L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
10         }
11 
12         loadFromUriLock.lock();
13         Bitmap bmp;
14         try {
15             checkTaskNotActual();
16 
17             bmp = configuration.memoryCache.get(memoryCacheKey);
18             if (bmp == null || bmp.isRecycled()) {
19                 bmp = tryLoadBitmap();
20                 if (bmp == null) return; // listener callback already was fired
21 
22                 checkTaskNotActual();
23                 checkTaskInterrupted();
24 
25                 if (options.shouldPreProcess()) {
26                     L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
27                     bmp = options.getPreProcessor().process(bmp);
28                     if (bmp == null) {
29                         L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
30                     }
31                 }
32 
33                 if (bmp != null && options.isCacheInMemory()) {
34                     L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
35                     configuration.memoryCache.put(memoryCacheKey, bmp);
36                 }
37             } else {
38                 loadedFrom = LoadedFrom.MEMORY_CACHE;
39                 L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
40             }
41 
42             if (bmp != null && options.shouldPostProcess()) {
43                 L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
44                 bmp = options.getPostProcessor().process(bmp);
45                 if (bmp == null) {
46                     L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
47                 }
48             }
49             checkTaskNotActual();
50             checkTaskInterrupted();
51         } catch (TaskCancelledException e) {
52             fireCancelEvent();
53             return;
54         } finally {
55             loadFromUriLock.unlock();
56         }
57 
58         DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
59         runTask(displayBitmapTask, syncLoading, handler, engine);
60     }

在这里使用了可重入锁机制来保证并发操作时数据的完整性。先从缓存中获取到的bitmap==null,看19行,来看这个方法。

 1 private Bitmap tryLoadBitmap() throws TaskCancelledException {
 2         Bitmap bitmap = null;
 3         try {
 4             File imageFile = configuration.diskCache.get(uri);
 5             if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
 6                 L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
 7                 loadedFrom = LoadedFrom.DISC_CACHE;
 8 
 9                 checkTaskNotActual();
10                 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
11             }
12             if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
13                 L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
14                 loadedFrom = LoadedFrom.NETWORK;
15 
16                 String imageUriForDecoding = uri;
17                 if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
18                     imageFile = configuration.diskCache.get(uri);
19                     if (imageFile != null) {
20                         imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
21                     }
22                 }
23 
24                 checkTaskNotActual();
25                 bitmap = decodeImage(imageUriForDecoding);
26 
27                 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
28                     fireFailEvent(FailType.DECODING_ERROR, null);
29                 }
30             }
31         } 
            ....45         return bitmap;
46     }

在这个方法返回了一个解码后的bitmap,是从磁盘读取文件或者网络中获取的。获取到bitmap后就回到run方法的58行。这里将bmp, imageLoadingInfo, engine等封装了一个runnable的实现类DisplayBitmapTask中。

同样来看一下这个runnable的run方法。

 1 @Override
 2     public void run() {
 3         if (imageAware.isCollected()) {
 4             L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
 5             listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
 6         } else if (isViewWasReused()) {
 7             L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
 8             listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
 9         } else {
10             L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
11             displayer.display(bitmap, imageAware, loadedFrom);
12             engine.cancelDisplayTaskFor(imageAware);
13             listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
14         }
15     }

第11行就回到了display方法,去给我们的ImageView设置bitmap。到此就完成了图片在控件中的展示过程。取消这次任务,并回调监听器的onLoadingComplete方法。

接下来我们来分析这个框架是如何加载图片的bitmap的。回到

tryLoadBitmap()方法的第10行或者25行,看decodeImage(imageUriForDecoding);这个方法
1 private Bitmap decodeImage(String imageUri) throws IOException {
2         ViewScaleType viewScaleType = imageAware.getScaleType();
3         ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
4                 getDownloader(), options);
5         return decoder.decode(decodingInfo);
6     }

图片的uri等信息被封装到了ImageDecodingInfo对象当中。其中getDownloader()返回了ImageDownloader这个接口实现类对象的引用。 那这个ImageDownloader的对象是从哪里来的呢?我们看LoadAndDisplayImageTask的构造方式中,

downloader = configuration.downloader; networkDeniedDownloader = configuration.networkDeniedDownloader; slowNetworkDownloader = configuration.slowNetworkDownloader;

而configuration 是最初在Application类中初始化 ImageLoader 的时候用户传递过来的参数。初始化ImageLoaderConfiguration.Builder时为其设置imageDownloader,如果不设置该变量,则在build()的时候在initEmptyFieldsWithDefaultValues方法中为其初始化一个默认值:

1 if (downloader == null) {
2              downloader = DefaultConfigurationFactory.createImageDownloader(context);
3  }

其实也就是ImageDownloader的一个实现类的对象BaseImageDownloader对象

1 public static ImageDownloader createImageDownloader(Context context) {
2    return new BaseImageDownloader(context);
3  }

而ImageDownloader对外提供了getStream方法,根据uri获取输入流信息。

1 InputStream getStream(String imageUri, Object extra)

然后我们来看decodeImage方法中的decode做了什么。ImageDecoder是一个接口,里面只有一个decode方法,它的主要作用就是将获取到的InputStream转换成Bitmap。看它的实现类BaseImageDecoder

 1 public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
 2         Bitmap decodedBitmap;
 3         ImageFileInfo imageInfo;
 4 
 5         InputStream imageStream = getImageStream(decodingInfo);
 6         if (imageStream == null) {
 7             L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
 8             return null;
 9         }
10         try {
11             imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
12             imageStream = resetStream(imageStream, decodingInfo);
13             Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
14             decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
15         } finally {
16             IoUtils.closeSilently(imageStream);
17         }
18 
19         if (decodedBitmap == null) {
20             L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
21         } else {
                 //将Bitmap缩放和旋转成满足需求的Bitmap
22             decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
23                     imageInfo.exif.flipHorizontal);
24         }
25         return decodedBitmap;
26     }

第5行,这个方法调用的就是ImageDownloader的 getStream,并根据uri的Scheme信息判断这个图片是在哪里,从这里真正去获取bitmap。

 1 @Override
 2     public InputStream getStream(String imageUri, Object extra) throws IOException {
 3         switch (Scheme.ofUri(imageUri)) {
 4             case HTTP:
 5             case HTTPS:
 6                 return getStreamFromNetwork(imageUri, extra);
 7             case FILE:
 8                 return getStreamFromFile(imageUri, extra);
 9             case CONTENT:
10                 return getStreamFromContent(imageUri, extra);
11             case ASSETS:
12                 return getStreamFromAssets(imageUri, extra);
13             case DRAWABLE:
14                 return getStreamFromDrawable(imageUri, extra);
15             case UNKNOWN:
16             default:
17                 return getStreamFromOtherSource(imageUri, extra);
18         }
19     }

我们以网络获取为例进行分析:

 1 protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
 2         HttpURLConnection conn = createConnection(imageUri, extra);
 3 
 4         int redirectCount = 0;
 5         while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
 6             conn = createConnection(conn.getHeaderField("Location"), extra);
 7             redirectCount++;
 8         }
 9 
10         InputStream imageStream;
11         try {
12             imageStream = conn.getInputStream();
13         } catch (IOException e) {
14             // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
15             IoUtils.readAndCloseStream(conn.getErrorStream());
16             throw e;
17         }
18         if (!shouldBeProcessed(conn)) {
19             IoUtils.closeSilently(imageStream);
20             throw new IOException("Image request failed with response code " + conn.getResponseCode());
21         }
22 
23         return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
24     }

通过HttpURLConnection从服务器获取一个InputStream,封装在了一个自定义的ContentLengthInputStream中。获取到InputStream后返回到decode方法的11行.

 1 protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
 2             throws IOException {
 3         Options options = new Options();
 4         options.inJustDecodeBounds = true; //true那么将不返回实际的bitmap对象,不给其分配内存空间但是可以得到一些解码边界信息即图片大小等信息  
 5         BitmapFactory.decodeStream(imageStream, null, options);
 6 
 7         ExifInfo exif;
 8         String imageUri = decodingInfo.getImageUri();
 9         if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
10             exif = defineExifOrientation(imageUri);
11         } else {
12             exif = new ExifInfo();
13         }
14         return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
15     }

在这个方法中第一次调用 BitmapFactory.decodeStream(imageStream, null, options); 获取图片的大小解码边界等信息

decode方法的第12行:是重新获取bitmap,这个我也没有搞清楚它为什么又获取一遍,可能是因为调用过来一次decodeStream,但是第一次调用只是为了得到图片的一些信息,并未得到bitmap,所以需要重新对一个新的inputStream进行decode,
只是这个时候已经知道了图片的大小 格式等信息了。就可以直接返回bitmap。

至此,过去bitmap的过程已经完毕,这就是display时调用的imageAware.setImageBitmap(bitmap);中的bitmap的由来。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • jquery判空 string类型的日期比较大小

    if(value.length<=0){ alert("kongzhi"); }

    yuanyuan
  • ImageLoader

    ImageLoader在github上的地址:https://github.com/nostra13/Android-Universal-Image-Loade...

    yuanyuan
  • Java 运算符 % 和 /

    yuanyuan
  • 【死磕 Spring】—– IOC 之构造函数实例化 bean

    createBeanInstance() 用于实例化 bean,它会根据不同情况选择不同的实例化策略来完成 bean 的初始化,主要包括:

    用户1655470
  • 关于 MyBatis 我总结了 10 种通用的写法

    foreach元素的属性主要有item,index,collection,open,separator,close。

    逆锋起笔
  • 数字IC设计经典笔试题之【verilog篇】

    HDL语言是分层次的、类型的,最常用的层次概念有系统与标准级、功能模块级,行为级,寄存器传输级和门级。

    数字芯片社区
  • 高端FPGA揭秘之存储及高速接口

    来源:内容由「网络交换FPGA」编译自「eejournal」,作者:Kevin Morris,谢谢。

    网络交换FPGA
  • 豆瓣isbn查询api接口制作教程【源码已开源】

    之前腾讯出了个私房书柜项目的云开发教程,所用的接口就是豆瓣的isbn查询接口,然后它就一下大火了,但是大概在四月份左右,它的接口就失效了,顺便连带的开放平台都关...

    许坏
  • 微信公众号对话框回复消息链接跳转小程序

    在项目过程之中,可能会遇到这样的需求,点击微信公众平台开发的界面里的某个图标或者文字,跳转到小程序,这样更加有利于用户去访问小程序,如果是把小程序的卡片直接插入...

    祈澈菇凉
  • kafka的offset相关知识

    由于一个partition只能固定的交给一个消费者组中的一个消费者消费,因此Kafka保存offset时并不直接为每个消费者保存,而是以 groupid-top...

    小勇DW3

扫码关注云+社区

领取腾讯云代金券