Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加 载,缓存和显示。
ImageLoader 是最早开源的 Android 图片缓存库, 强大的缓存机制, 早期使用这个图片加载框架的Android应用非常多。
首先说它的简单使用;
第一步:加入imageLoader的jar。地址贴下。
http://www.java2s.com/Code/Jar/u/Downloaduniversalimageloaderjar.htm
第二步,自定义一个以MyApplication命名的类,该类继承Application,并在AndroidManifest.xml文件中对MyApplication进行注册。
第三步,在MyApplication中进行ImageLoader的初始化配置,包括加载或解码过程中发生错误显示的图片,设置下载的图片是否缓存在内存中等等。
基础代码如下:
ImageLoader使用的是单例模式。在项目中只需要设置一次就可以。
ImageLoader的使用方式:
- 根据url把图片展示到imageView控件上(异步加载)
ImageLoader.getInstance().displayImage(imageUri, imageView);
方法displayImage是异步加载,可以不设置监听,待图片资源加载完毕,会自动把图片展示到控件上。
- 根据url把图片展示到imageView控件上,并监听图片加载是否完毕(异步加载)
ImageLoader.getInstance().displayImage(imageUrl, imageView, null , new
SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
//显示imageview
} }, new ImageLoadingProgressListener(){
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
//total总进度,current当前加载进度
} });
方法loadImage是异步加载,一般需要对加载过程设置监听,待图片资源加载完毕,再手动操作把图片展示到控件上。
- 根据url获取到图片文件并返回为位图格式对象(同步加载)
Bitmap bmp = ImageLoader.getInstance().loadImageSync(imageUri);
方法loadImageSync是同步加载,待图片资源加载完毕,直接返回位图资源对象
ImageLoader的简单使用就介绍到此,接下来对它的源码进行读解。
先上一张ImageLoader的设计图镇楼。
从 ImageLoader 的总体设计图看,整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor ,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。
简单的讲就是 ImageLoader 收到加载及显示图片的任务,并将它交给 ImageLoaderEngine,ImageLoaderEngine 分发任务到具体线程池去执行,任务通过 Cache 及 ImageDownloader 获取图片,中间可能经过 BitmapProcessor 和 ImageDecoder 处理,最终转换为Bitmap 交给 BitmapDisplayer 在 ImageAware 中显示。
看下ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);源码,
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
displayImage(uri, new ImageViewAware(imageView), options, null, null);
}
从上面的代码中,我们可以看出,它会将ImageView转换成ImageViewAware, ImageViewAware主要是ImageView进行一个包装,将ImageView的强引用变成弱引用,当内存不足的时候,可以更好的回收ImageView对象,还有就是获取ImageView的宽度和高度。这使得我们可以根据ImageView的宽高去对图片进行一个裁剪,减少内存的使用。
下面看displayImage具体的方法:
第1行代码是检查ImageLoaderConfiguration是否初始化,这个初始化是在Application中进行。
第12-21行主要是针对url为空的时候做的处理 。
第13行代码中,ImageLoaderEngine中存在一个HashMap,用来记录正在加载的任务,加载图片的时候会将ImageView的id和图片的url加上尺寸加入到HashMap中,加载完成之后会将其移除,然后将DisplayImageOptions的imageResForEmptyUri的图片设置给ImageView,最后回调给ImageLoadingListener接口通知这次任务完成。
第1行主要是将ImageView的宽高封装成ImageSize对象,如果获取ImageView的宽高为0,就会使用手机屏幕的宽高作为ImageView的宽高,我们在使用ListView,GridView去加载图片的时候,第一页获取宽度是0,所以第一页使用的手机的屏幕宽高,后面的获取的都是控件本身的大小。
第7行从内存缓存中获取Bitmap对象,我们可以再ImageLoaderConfiguration中配置内存缓存逻辑,默认使用的是LruMemoryCache。
第11行中有一个判断,我们如果在DisplayImageOptions中设置了postProcessor就进入true逻辑,不过默认postProcessor是为null的,BitmapProcessor接口主要是对Bitmap进行处理,这个框架并没有给出相对应的实现,如果我们有自己的需求的时候可以自己实现BitmapProcessor接口。
第22 -23行是将Bitmap设置到ImageView上面,这里我们可以在DisplayImageOptions中配置显示需求displayer,默认使用的是SimpleBitmapDisplayer,直接将Bitmap设置到ImageView上面,我们可以配置其他的显示逻辑, 他这里提供了FadeInBitmapDisplayer(透明度从0-1)RoundedBitmapDisplayer(4个角是圆弧)等, 然后回调到ImageLoadingListener接口。
这段代码主要是Bitmap不在内存缓存,从文件中或者网络里面获取bitmap对象,实例化一个LoadAndDisplayImageTask对象,LoadAndDisplayImageTask实现了Runnable,如果配置了isSyncLoading为true, 直接执行LoadAndDisplayImageTask的run方法,表示同步,默认是false,将LoadAndDisplayImageTask提交给线程池对象。
LoadAndDisplayImageTask的run()中,如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑。先看waitIfPaused()方法。
我们在使用ListView,GridView去加载图片的时候,有时候为了滑动更加的流畅,我们会选择手指在滑动或者猛地一滑动的时候不去加载图片,所以才提出了这么一个方法。
这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling 控制猛的滑动ListView,GridView是否停止加载图片
除此之外,这个方法的返回值由isTaskNotActual()决定。
再看看isTaskNotActual()的源码。
isViewCollected()是判断我们ImageView是否被垃圾回收器回收了,如果回收了,LoadAndDisplayImageTask方法的run()就直接返回了,isViewReused()判断该ImageView是否被重用,被重用run()方法也直接返回。
主要是ListView,GridView我们会复用item对象,假如我们先去加载ListView,GridView第一页的图片的时候,第一页图片还没有全部加载完我们就快速的滚动,isViewReused()方法就会避免这些不可见的item去加载图片,而直接加载当前界面的图片。
- ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
- L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
- if (loadFromUriLock.isLocked()) {
- L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
- }
- loadFromUriLock.lock();
- Bitmap bmp;
- try {
- checkTaskNotActual();
- bmp = configuration.memoryCache.get(memoryCacheKey);
- if (bmp == null || bmp.isRecycled()) {
- bmp = tryLoadBitmap();
- if (bmp == null) return; // listener callback already was fired
- checkTaskNotActual();
- checkTaskInterrupted();
- if (options.shouldPreProcess()) {
- L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
- bmp = options.getPreProcessor().process(bmp);
- if (bmp == null) {
- L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
- }
- }
- if (bmp != null && options.isCacheInMemory()) {
- L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
- configuration.memoryCache.put(memoryCacheKey, bmp);
- }
- } else {
- loadedFrom = LoadedFrom.MEMORY_CACHE;
- L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
- }
- if (bmp != null && options.shouldPostProcess()) {
- L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
- bmp = options.getPostProcessor().process(bmp);
- if (bmp == null) {
- L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
- }
- }
- checkTaskNotActual();
- checkTaskInterrupted();
- } catch (TaskCancelledException e) {
- fireCancelEvent();
- return;
- } finally {
- loadFromUriLock.unlock();
- }
第1行代码有一个loadFromUriLock,这个是一个锁,获取锁的方法在ImageLoaderEngine类的getLockForUri()方法中。
这个锁对象与图片的url是相互对应的,假如在一个ListView中,某个item正在获取图片的过程中,而此时我们将这个item滚出界面之后又将其滚进来,滚进来之后如果没有加锁,该item又会去加载一次图片,假设在很短的时间内滚动很频繁,那么就会出现多次去网络上面请求图片,所以这里根据图片的Url去对应一个ReentrantLock对象,让具有相同Url的请求就会在第7行等待,等到这次图片加载完成之后,ReentrantLock就被释放,刚刚那些相同Url的请求就会继续执行第7行下面的代码。
第12行,它们会先从内存缓存中获取一遍,如果内存缓存中没有在去执行下面的逻辑,所以ReentrantLock的作用就是避免这种情况下重复的去从网络上面请求图片。
第14行的方法tryLoadBitmap(),这个方法确实也有点长,我先告诉大家,这里面的逻辑是先从文件缓存中获取有没有Bitmap对象,如果没有在去从网络中获取,然后将bitmap保存在文件系统中。
先判断文件缓存中有没有该文件,如果有的话,直接去调用decodeImage()方法去解码图片,该方法里面调用BaseImageDecoder类的decode()方法,根据ImageView的宽高,ScaleType去裁剪图片再看看tryLoadBitmap()方法。
第1行表示从文件缓存中获取的Bitmap为null,或者宽高为0,就去网络上面获取Bitmap。
第6行代码是否配置了DisplayImageOptions的isCacheOnDisk,表示是否需要将Bitmap对象保存在文件系统中,一般我们需要配置为true, 默认是false这个要注意下,然后就是执行tryCacheImageOnDisk()方法,去服务器上面拉取图片并保存在本地文件中。
- private Bitmap decodeImage(String imageUri) throws IOException {
- ViewScaleType viewScaleType = imageAware.getScaleType();
- ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
- getDownloader(), options);
- return decoder.decode(decodingInfo);
- }
- /** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */
- private boolean tryCacheImageOnDisk() throws TaskCancelledException {
- L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
- boolean loaded;
- try {
- loaded = downloadImage();
- if (loaded) {
- int width = configuration.maxImageWidthForDiskCache;
- int height = configuration.maxImageHeightForDiskCache;
- if (width > 0 || height > 0) {
- L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
- resizeAndSaveImage(width, height); // TODO : process boolean result
- }
- }
- } catch (IOException e) {
- L.e(e);
- loaded = false;
- }
- return loaded;
- }
- private boolean downloadImage() throws IOException {
- InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
- return configuration.diskCache.save(uri, is, this);
- }
第6行的downloadImage()方法是负责下载图片,并将其保持到文件缓存中,将下载保存Bitmap的进度回调到IoUtils.CopyListener接口的onBytesCopied(int current, int total)方法中,所以我们可以设置ImageLoadingProgressListener接口来获取图片下载保存的进度,这里保存在文件系统中的图片是原图。
第16-17行,获取ImageLoaderConfiguration是否设置保存在文件系统中的图片大小,如果设置了maxImageWidthForDiskCache和maxImageHeightForDiskCache,会调用resizeAndSaveImage()方法对图片进行裁剪然后在替换之前的原图,保存裁剪后的图片到文件系统的。
在Application中实例化ImageLoaderConfiguration的时候设置maxImageWidthForDiskCache和maxImageHeightForDiskCache可以设置原图和缩略图。
6-12行是否要对Bitmap进行处理,这个需要自行实现,14-17就是将图片保存到内存缓存中。
最后这两行代码就是一个显示任务,直接看DisplayBitmapTask类的run()方法。
假如ImageView被回收了或者被重用了,回调给ImageLoadingListener接口,否则就调用BitmapDisplayer去显示Bitmap。
ImageLoadader中用了很多的设计模式,比如建造者模式,装饰模式,代理模式,策略模式等等,方便我们使用者扩展。