转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/39057201),请尊重他人的辛勤劳动成果,谢谢!
本篇文章主要是带大家从源码的角度上面去解读这个强大的图片加载框架,自己很久没有写文章了,感觉生疏了许多,距离上一篇文章三个月多了,确实是自己平常忙,换了工作很多东西都要去看去理解,然后加上自己也懒了,没有以前那么有激情了,我感觉这节奏不对,我要继续保持以前的激情,正所谓好记性不如烂笔头,有时候自己也会去翻看下之前写的东西,我觉得知识写下来比在脑海中留存的更久,今天就给大家来读一读这个框架的源码,我感觉这个图片加载框架确实写的很不错,读完代码自己也学到了很多。我希望大家可以先去看下Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用, Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解 ,我希望大家可以坚持看完,看完了对你绝对是有收获的。
- ImageView mImageView = (ImageView) findViewById(R.id.image);
- String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";
- //显示图片的配置
- DisplayImageOptions options = new DisplayImageOptions.Builder()
- .showImageOnLoading(R.drawable.ic_stub)
- .showImageOnFail(R.drawable.ic_error)
- .cacheInMemory(true)
- .cacheOnDisk(true)
- .bitmapConfig(Bitmap.Config.RGB_565)
- .build();
- ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);
- public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
- displayImage(uri, new ImageViewAware(imageView), options, null, null);
- }
接下来看具体的displayImage方法啦,由于这个方法代码量蛮多的,所以这里我分开来读
- checkConfiguration();
- if (imageAware == null) {
- throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
- }
- if (listener == null) {
- listener = emptyListener;
- }
- if (options == null) {
- options = configuration.defaultDisplayImageOptions;
- }
- if (TextUtils.isEmpty(uri)) {
- engine.cancelDisplayTaskFor(imageAware);
- listener.onLoadingStarted(uri, imageAware.getWrappedView());
- if (options.shouldShowImageForEmptyUri()) {
- imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
- } else {
- imageAware.setImageDrawable(null);
- }
- listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
- return;
- }
12-21行主要是针对url为空的时候做的处理,第13行代码中,ImageLoaderEngine中存在一个HashMap,用来记录正在加载的任务,加载图片的时候会将ImageView的id和图片的url加上尺寸加入到HashMap中,加载完成之后会将其移除,然后将DisplayImageOptions的imageResForEmptyUri的图片设置给ImageView,最后回调给ImageLoadingListener接口告诉它这次任务完成了。
- ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
- String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
- engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
- listener.onLoadingStarted(uri, imageAware.getWrappedView());
- Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
- if (bmp != null && !bmp.isRecycled()) {
- L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
- if (options.shouldPostProcess()) {
- ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
- options, listener, progressListener, engine.getLockForUri(uri));
- ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
- defineHandler(options));
- if (options.isSyncLoading()) {
- displayTask.run();
- } else {
- engine.submit(displayTask);
- }
- } else {
- options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
- listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
- }
- }
第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接口
- if (options.shouldShowImageOnLoading()) {
- imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
- } else if (options.isResetViewBeforeLoading()) {
- imageAware.setImageDrawable(null);
- }
- ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
- options, listener, progressListener, engine.getLockForUri(uri));
- LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
- defineHandler(options));
- if (options.isSyncLoading()) {
- displayTask.run();
- } else {
- engine.submit(displayTask);
- }
接下来我们就看LoadAndDisplayImageTask的run(), 这个类还是蛮复杂的,我们还是一段一段的分析
- if (waitIfPaused()) return;
- if (delayIfNeed()) return;
如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑, 接下来我们先看看waitIfPaused()
- private boolean waitIfPaused() {
- AtomicBoolean pause = engine.getPause();
- if (pause.get()) {
- synchronized (engine.getPauseLock()) {
- if (pause.get()) {
- L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
- try {
- engine.getPauseLock().wait();
- } catch (InterruptedException e) {
- L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
- return true;
- }
- L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
- }
- }
- }
- return isTaskNotActual();
- }
这个方法是干嘛用呢,主要是我们在使用ListView,GridView去加载图片的时候,有时候为了滑动更加的流畅,我们会选择手指在滑动或者猛地一滑动的时候不去加载图片,所以才提出了这么一个方法,那么要怎么用呢? 这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling 控制猛的滑动ListView,GridView是否停止加载图片
除此之外,这个方法的返回值由isTaskNotActual()决定,我们接着看看isTaskNotActual()的源码
- private boolean isTaskNotActual() {
- return isViewCollected() || isViewReused();
- }
- 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();
- }
- ReentrantLock getLockForUri(String uri) {
- ReentrantLock lock = uriLocks.get(uri);
- if (lock == null) {
- lock = new ReentrantLock();
- uriLocks.put(uri, lock);
- }
- return lock;
- }
来到第12行,它们会先从内存缓存中获取一遍,如果内存缓存中没有在去执行下面的逻辑,所以ReentrantLock的作用就是避免这种情况下重复的去从网络上面请求图片。
第14行的方法tryLoadBitmap(),这个方法确实也有点长,我先告诉大家,这里面的逻辑是先从文件缓存中获取有没有Bitmap对象,如果没有在去从网络中获取,然后将bitmap保存在文件系统中,我们还是具体分析下
- File imageFile = configuration.diskCache.get(uri);
- if (imageFile != null && imageFile.exists()) {
- L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
- loadedFrom = LoadedFrom.DISC_CACHE;
- checkTaskNotActual();
- bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
- }
- if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
- L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
- loadedFrom = LoadedFrom.NETWORK;
- String imageUriForDecoding = uri;
- if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
- imageFile = configuration.diskCache.get(uri);
- if (imageFile != null) {
- imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
- }
- }
- checkTaskNotActual();
- bitmap = decodeImage(imageUriForDecoding);
- if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
- fireFailEvent(FailType.DECODING_ERROR, null);
- }
- }
- 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);
- }
第16-17行,获取ImageLoaderConfiguration是否设置保存在文件系统中的图片大小,如果设置了maxImageWidthForDiskCache和maxImageHeightForDiskCache,会调用resizeAndSaveImage()方法对图片进行裁剪然后在替换之前的原图,保存裁剪后的图片到文件系统的,之前有同学问过我说这个框架保存在文件系统的图片都是原图,怎么才能保存缩略图,只要在Application中实例化ImageLoaderConfiguration的时候设置maxImageWidthForDiskCache和maxImageHeightForDiskCache就行了
- 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);
- }
- DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
- runTask(displayBitmapTask, syncLoading, handler, engine);
- @Override
- public void run() {
- if (imageAware.isCollected()) {
- L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
- listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
- } else if (isViewWasReused()) {
- L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
- listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
- } else {
- L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
- displayer.display(bitmap, imageAware, loadedFrom);
- engine.cancelDisplayTaskFor(imageAware);
- listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
- }
- }
假如ImageView被回收了或者被重用了,回调给ImageLoadingListener接口,否则就调用BitmapDisplayer去显示Bitmap
文章写到这里就已经写完了,不知道大家对这个开源框架有没有进一步的理解,这个开源框架设计也很灵活,用了很多的设计模式,比如建造者模式,装饰模式,代理模式,策略模式等等,这样方便我们去扩展,实现我们想要的功能,今天的讲解就到这了,有对这个框架不明白的地方可以在下面留言,我会尽量为大家解答的。