从代码分析Android-Universal-Image-Loader的图片加载、显示流程

UNIVERSAL IMAGE LOADER. PART 3(四个DisplayImage重载方法详解)中,我们学习了Android-Universal-Image-Loader(以下简称UIL)中四个DisplayImage重载方法的使用,如果你还没有学习,最好先返回去看看,不然可能不理解这篇文章。在这篇文章中我们将主要探讨Android-Universal-Image-Loader的主要流程和这些流程相关的类的分析。

我们先了解一下UIL加载图片的流程(可以通过查看ImageLoader.displayImage(…)方法分析得出),如下图

从上图中,我们可以看出,UIL加载图片的一般流程是先判断内存中是否有对应的Bitmap,再判断磁盘(disk)中是否有,如果没有就从网络中加载。最后根据原先在UIL中的配置判断是否需要缓存Bitmap到内存或磁盘中。Bitmap加载完后,就对它进行解析,然后显示到特定的ImageView中。

有了对UIL对图片加载和处理流程的初步认识之后,我们就可以着手分析它的源代码了。先从ImageLoader.displayImage(...)入手,毕竟一切都因它而始。

 1     public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
 2             ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
 3         //检查UIL的配置是否被初始化
 4         checkConfiguration();
 5         if (imageAware == null) {
 6             throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
 7         }
 8         if (listener == null) {
 9             listener = emptyListener;
10         }
11         if (options == null) {
12             options = configuration.defaultDisplayImageOptions;
13         }
14
15         if (TextUtils.isEmpty(uri)) {
16             engine.cancelDisplayTaskFor(imageAware);
17             listener.onLoadingStarted(uri, imageAware.getWrappedView());
18             if (options.shouldShowImageForEmptyUri()) {
19                 imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
20             } else {
21                 imageAware.setImageDrawable(null);
22             }
23             listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
24             return;
25         }
26         //计算Bitmap的大小,以便后面解析图片时用
27         ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
28         String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
29         engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
30
31         listener.onLoadingStarted(uri, imageAware.getWrappedView());
32         //Bitmap是否缓存在内存?
33         Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
34         if (bmp != null && !bmp.isRecycled()) {
35             L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
36
37             if (options.shouldPostProcess()) {
38                 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
39                         options, listener, progressListener, engine.getLockForUri(uri));
40                 //处理并显示图片
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                 //显示图片
50                 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
51                 listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
52             }
53         } else {
54             if (options.shouldShowImageOnLoading()) {
55                 imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
56             } else if (options.isResetViewBeforeLoading()) {
57                 imageAware.setImageDrawable(null);
58             }
59
60             ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
61                     options, listener, progressListener, engine.getLockForUri(uri));
62             //启动一个线程,加载并显示图片
63             LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
64                     defineHandler(options));
65             if (options.isSyncLoading()) {
66                 displayTask.run();
67             } else {
68                 engine.submit(displayTask);
69             }
70         }
71     }

代码有点多,但是有很多代码是进行异常判断处理和函数的回调,为了先把握整体的流程,我们先放弃细节方面的追踪。基本上重要的处理流程我都有用注释标出。不过,从这段代码中我们也可以看出这段代码的结构非常清晰。对图片的整个的加载流程都有对应的监听接口(ImageLoadingListener.onLoadingStarted,ImageLoadingListener.onLoadingComplete,ImageLoadingListener这个类就是用来监听图片的加载过程的),也就是说整个的图片加载过程程序员都可以进行相应的处理。我们先关注一下图片从无到有的加载过程,毕竟这部分是大家最为关心的。看到第63行中的LoadAndDisplayImageTask,跟进LoadAndDisplayImageTask.run()方法中。在这个run()方法中,除了 bmp = tryLoadBitmap();这一句是对图片进行加载,其他的函数都是对Bitmap进行处理或者显示。我们继续进入看看。

 1 private Bitmap tryLoadBitmap() throws TaskCancelledException {
 2         Bitmap bitmap = null;
 3         try {
 4             //尝试从磁盘缓存中读取Bitmap
 5             File imageFile = configuration.diskCache.get(uri);
 6             if (imageFile != null && imageFile.exists()) {
 7                 L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
 8                 loadedFrom = LoadedFrom.DISC_CACHE;
 9
10                 checkTaskNotActual();
11                 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
12             }
13             //没有缓存在磁盘,从网络中下载图片
14             if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
15                 L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
16                 loadedFrom = LoadedFrom.NETWORK;
17
18                 String imageUriForDecoding = uri;
19                 if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
20                     imageFile = configuration.diskCache.get(uri);
21                     if (imageFile != null) {
22                         imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
23                     }
24                 }
25
26                 checkTaskNotActual();
27                 bitmap = decodeImage(imageUriForDecoding);
28
29                 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
30                     fireFailEvent(FailType.DECODING_ERROR, null);
31                 }
32             }
33         } catch (IllegalStateException e) {
34             fireFailEvent(FailType.NETWORK_DENIED, null);
35         } catch (TaskCancelledException e) {
36             throw e;
37         } catch (IOException e) {
38             L.e(e);
39             fireFailEvent(FailType.IO_ERROR, e);
40         } catch (OutOfMemoryError e) {
41             L.e(e);
42             fireFailEvent(FailType.OUT_OF_MEMORY, e);
43         } catch (Throwable e) {
44             L.e(e);
45             fireFailEvent(FailType.UNKNOWN, e);
46         }
47         return bitmap;
48     }

从3~12行是尝试从磁盘缓存中加载Bitmap。第19行判断磁盘中是否有缓存,就开始进行网络下载(tryCacheImageOnDisk())。在tryCacheImageOnDisk()函数中有个tryCacheImageOnDisk()的 loaded = downloadImage()这行进行图片下载。

    private boolean downloadImage() throws IOException {
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        return configuration.diskCache.save(uri, is, this);
    }

这个函数做的事情很简单,就是获取一个实现Image Downloader的downloader(当然这里,作者根据网络情况将downloader分为慢速(slowNetworkDownloader)、正常速度(downloader)、网络拒绝(networkDeniedDownloader)情况下的download,在这里我们不展开,你只要知道他们是imageDownloader接口的实现者就行,后面的文章会探讨这个问题),然后利用Disk Cache将Bitmap写入磁盘缓存中。返回到之前我们进入downloadImage()函数中的tryLoadBitmap(),在将图片缓存到磁盘中。是否缓存到磁盘跟配置有关)后,紧接着调用 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));解析图片。进入decodeImage()函数中,我们发现UIL调用Image Decoder进行图片的解析。

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     }

decode()函数最终是调用BaseImageDecoder.decode()方法进行解析的,这个利用之前获得的inputStream,直接从它身上读取数据,然后进行解析,并对整个下载任务的网络接口进行重置。

 1 public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
 2         Bitmap decodedBitmap;
 3         ImageFileInfo imageInfo;
 4
 5         InputStream imageStream = getImageStream(decodingInfo);
 6         try {
 7             imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
 8             imageStream = resetStream(imageStream, decodingInfo);
 9             Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
10             decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
11         } finally {
12             IoUtils.closeSilently(imageStream);
13         }
14
15         if (decodedBitmap == null) {
16             L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
17         } else {
18             decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
19                     imageInfo.exif.flipHorizontal);
20         }
21         return decodedBitmap;
22     }

接下来,有了解析好的Bitmap对象后,剩下的就是在Image View对象中显示它了。我们回到文章一开始介绍到的ImageLoader.displayImage(...)函数中(相关的代码在文章的开头处可以看到)。

为了方便,我还是将ImageLoader.displayImage(...)中涉及的代码贴在下面。

1         DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
2         runTask(displayBitmapTask, syncLoading, handler, engine);

我们进去DisplayBitmapTask.run()函数中看看。除去前面几行的ImageLoadingListener.ImageLoadingListener()代码,相关代码其实就一行 displayer.display(bitmap, imageAware, loadedFrom),它其实就是调用BitmapDisplayer这个对象将Bitmap对象显示到ImageView上。根据实现BitmapDisplayer接口的不同对象,还有SimpleBitmapDisplayer、FadeInBitmapDisplayer、RoundedBitmapDisplayer、RoundedVignetteBitmapDisplayer这5种对象。

最后,让我们用任务流图概况以上的处理流程中对应接口。

在接下去的文章中,我们会介绍UIL中的包设计、缓冲、下载、多任务机制。

时间: 2024-11-02 23:35:23

从代码分析Android-Universal-Image-Loader的图片加载、显示流程的相关文章

Android BitmapFactory.Options 解决大图片加载OOM问题

当我们在Android使用bitmap加载图片过程中,它会将整张图片所有像素都存在内存中,由于Android对图片内存使用的限制,很容易出现OOM(Out of Memory)问题. 为了避免此类问题我们可以采用BitmapFactory.Options或是使用第三方的图片加载库.如Fresco.Picasso等. BitmapFactory.Options 读取图片尺寸.类型 如文档所示: 如果BitmapFactory.Options中inJustDecodeBounds 字段设置为true

Android4.4 Framework分析——Android默认Home应用Launcher3的加载过程分析

本文主要介绍Android4.4默认Home应用Launcher3的启动过程和Launcher3的数据加载过程.Launcher的启动是开机时,ActivityManagerService准备好后开始的,下图是它的启动序列图: step1,SystemServer中,ActivityManagerService准备好了. step3, boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord targe

eclipse(有ADT插件)启动,出现Android SDK Content Loader 0%,加载不了问题的解决

系统:Win8.1-32位 软件:ADT-Bundle  23.0 我碰到的问题是每次开启都无法加载,不管是重启系统还是其他情况. 找到两种解决方法: 1.删掉workspace下的.metadata\.plugins\org.eclipse.core.resources\.projects文件夹,重启eclipse. 2.删掉C:\Users\用户名\.android文件夹下的所有内容,重启eclipse. 第一种方法我测试后还是没有解决,按照第二种方法做之后就顺利解决了.希望大家尽快解决问题

Android Launcher源码研究(二) 加载app流程1

今天主要分析Android Launcher源码中的一些重要类之间的关系,基本的加载流程.先来看一个类图 Launcher.java 是主Activity 在onCreate方法里面初始化了LauncherMode实例. LauncherApplication app = ((LauncherApplication)getApplication()); mModel = app.setLauncher(this); 直接进入LauncherApplication.java的方法 Launcher

Android Launcher源码研究(三) 加载app流程2

接上次的. 首先Launcher实现了LauncherModel.Callbacks接口,APP信息数据加载成功后 ,回调接口把app信息显示到Launcher的 workspace界面上,这个过程代码里面称为bind. 下面是个类调用过程的时序图,不是很标准,不过能表达基本调用顺序帮助我们理解. 首先就是Launcher OnCreate中调用LauncherMode startLoader方法,这里只看异步的方式 就是当前的页面下标为-1,加载所有app信息 mWorkspace.getCu

Android 图片加载框架Universal-Image-Loader源码解析

Universal-Image-Loader(项目地址)可以说是安卓知名图片开源框架中最古老.使用率最高的一个了.一张图片的加载对于安卓应用的开发也许是件简单的事,但是如果要同时加载大量的图片,并且图片用于ListView.GridView.ViewPager等控件,如何防止出现OOM.如何防止图片错位(因为列表的View复用功能).如何更快地加载.如何让客户端程序员用最简单的操作完成本来十分复杂的图片加载工作,成了全世界安卓应用开发程序员心头的一大难题,所幸有了Universal-Image-

Android中常见的图片加载框架

图片加载涉及到图片的缓存.图片的处理.图片的显示等.而随着市面上手机设备的硬件水平飞速发展,对图片的显示要求越来越高,稍微处理不好就会造成内存溢出等问题.很多软件厂家的通用做法就是借用第三方的框架进行图片加载. 开源框架的源码还是挺复杂的,但使用较为简单.大部分框架其实都差不多,配置稍微麻烦点,但是使用时一般只需要一行,显示方法一般会提供多个重载方法,支持不同需要.这样会减少很不必要的麻烦.同时,第三方框架的使用较为方便,这大大的减少了工作量.提高了开发效率.本文主要介绍四种常用的图片加载框架,

Android图片加载神器之Fresco,基于各种使用场景的讲解

转载请标明出处:http://blog.csdn.net/android_ls/article/details/53137867 Fresco是Facebook开源Android平台上一个强大的图片加载库,也是迄今为止Android平台上最强大的图片加载库. 优点:相对于其他开源的第三方图片加载库,Fresco拥有更好的内存管理和强大的功能,基本上能满足所有的日常使用场景. 缺点:整体比较大,不过目前的版本已做了拆分,你只需要导入你使用到的功能相关的库.从代码层面来说侵入性太强,体现在要使用它需

Android Universal Image Loader 使用

1. 功能介绍 1.1 Android Universal Image Loader Android Universal Image Loader 是一个强大的.可高度定制的图片缓存,本文简称为UIL. 简单的说 UIL 就做了一件事--获取图片并显示在相应的控件上. 1.2 基本使用 1.2.1 初始化 添加完依赖后在Application或Activity中初始化ImageLoader,如下: public class YourApplication extends Application

Android Universal Image Loader java.io.FileNotFoundException: http:/xxx/lxx/xxxx.jpg

前段时间在使用ImageLoader异步加载服务端返回的图片时总是出现 java.io.FileNotFoundException: http://xxxx/l046/10046137034b1c0db0.jpg at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177) at com.nostra13.universalimageloader.core.download.URL