前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Universal-Image-Loader源码解解析---display过程 + 获取bitmap过程

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

作者头像
yuanyuan
发布2019-09-10 18:05:38
8190
发布2019-09-10 18:05:38
举报
文章被收录于专栏:小满小满

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

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

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

代码语言:javascript
复制
 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的初始化过程:

代码语言:javascript
复制
 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的使用过程分析

代码语言:javascript
复制
ImageLoader.getInstance().displayImage(url, imageView, options);
代码语言:javascript
复制
displayImage方法有很大重载的方法,最终都会辗转调用到最复杂的这个重载方法:
代码语言:javascript
复制
 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:

代码语言:javascript
复制
"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).不能为空
代码语言:javascript
复制
3.DisplayImageOptions options :用于图像解码和显示,如果该参数为空则使用默认的option。
4.ImageSize targetSize:图像目标大小。 如果为空 - 大小将取决于视图。
5.ImageLoadingListener listener:用于图像加载过程监听。 如果在UI线程上调用此方法,则监听器在UI线程上触发事件
6.ImageLoadingProgressListener progressListener:图像加载进度监听。 如果在UI线程上调用此方法,则监听器在UI线程上触发事件。 应在{option选项}中启用缓存磁盘以使此侦听器正常工作。
代码语言:javascript
复制
这个方法比较长,它的逻辑比较清晰,主要做了下面的方法:
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。
代码语言:javascript
复制
所以我们之间看49行。点开display方法,咦,它是一个接口。
它有几个实现类分别实现不同的图片显示方法。如果在初始化options选项没有设置displayer()选项则默认使用SimpleBitmapDisplayer()正常显示一张图片。如果设置了如下
代码语言:javascript
复制
.displayer(new CircleBitmapDisplayer(Color.WHITE,5))则显示圆角图片。

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

代码语言:javascript
复制
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);

代码语言:javascript
复制
在这里涉及到了一个非常重要的类:ImageLoaderEngine,它负责{LoadAndDisplayImageTask显示任务}的执行。下面重点解析一下ImageLoaderEngine这个类。
engine这个对象是什么时候初始化的呢?请回到Imageloader对象的初始化方法init中,第15行:engine = new ImageLoaderEngine(configuration);
看一下ImageLoaderEngine的构造方法:
代码语言:javascript
复制
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方法:

代码语言:javascript
复制
 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方法

代码语言:javascript
复制
 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行,来看这个方法。

代码语言:javascript
复制
 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方法。

代码语言:javascript
复制
 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的。回到

代码语言:javascript
复制
tryLoadBitmap()方法的第10行或者25行,看decodeImage(imageUriForDecoding);这个方法
代码语言:javascript
复制
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方法中为其初始化一个默认值:

代码语言:javascript
复制
1 if (downloader == null) {
2              downloader = DefaultConfigurationFactory.createImageDownloader(context);
3  }

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

代码语言:javascript
复制
1 public static ImageDownloader createImageDownloader(Context context) {
2    return new BaseImageDownloader(context);
3  }

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

代码语言:javascript
复制
1 InputStream getStream(String imageUri, Object extra)

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

代码语言:javascript
复制
 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。

代码语言:javascript
复制
 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     }

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

代码语言:javascript
复制
 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行.

代码语言:javascript
复制
 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); 获取图片的大小解码边界等信息

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

至此,过去bitmap的过程已经完毕,这就是display时调用的imageAware.setImageBitmap(bitmap);中的bitmap的由来。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-03-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档