Universal-Image-Loader完全解析

相信大家平时做Android应用的时候,多少会接触到异步加载图片,或者加载大量图片的问题,而加载图片我们常常会遇到许多的问题,比如说图片的错乱,OOM等问题,对于新手来说,这些问题解决起来会比较吃力,所以就有很多的开源图片加载框架应运而生,比较著名的就是Universal-Image-Loader,相信很多朋友都听过或者使用过这个强大的图片加载框架,今天这篇文章就是对这个框架的基本介绍以及使用,主要是帮助那些没有使用过这个框架的朋友们。该项目存在于Github上面https://github.com/hgl888/Android-Universal-Image-Loader,我们可以先看看这个开源库存在哪些特征

  1. 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
  2. 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
  3. 支持图片的内存缓存,文件系统缓存或者SD卡缓存
  4. 支持图片下载过程的监听
  5. 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
  6. 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
  7. 提供在较慢的网络下对图片进行加载

当然上面列举的特性可能不全,要想了解一些其他的特性只能通过我们的使用慢慢去发现了,接下来我们就看看这个开源库的简单使用吧

新建一个Android项目,下载JAR包添加到工程libs目录下

新建一个MyApplication继承Application,并在onCreate()中创建ImageLoader的配置参数,并初始化到ImageLoader中代码如下

[java] view
plain
 copy

  1. package com.example.uil;
  2. import com.nostra13.universalimageloader.core.ImageLoader;
  3. import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
  4. import android.app.Application;
  5. public class MyApplication extends Application {
  6. @Override
  7. public void onCreate() {
  8. super.onCreate();
  9. //创建默认的ImageLoader配置参数
  10. ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
  11. //Initialize ImageLoader with configuration.
  12. ImageLoader.getInstance().init(configuration);
  13. }
  14. }

ImageLoaderConfiguration是图片加载器ImageLoader的配置参数,使用了建造者模式,这里是直接使用了createDefault()方法创建一个默认的ImageLoaderConfiguration,当然我们还可以自己设置ImageLoaderConfiguration,设置如下

[java] view
plain
 copy

  1. File cacheDir = StorageUtils.getCacheDirectory(context);
  2. ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
  3. .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
  4. .diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)
  5. .taskExecutor(...)
  6. .taskExecutorForCachedImages(...)
  7. .threadPoolSize(3) // default
  8. .threadPriority(Thread.NORM_PRIORITY - 1) // default
  9. .tasksProcessingOrder(QueueProcessingType.FIFO) // default
  10. .denyCacheImageMultipleSizesInMemory()
  11. .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
  12. .memoryCacheSize(2 * 1024 * 1024)
  13. .memoryCacheSizePercentage(13) // default
  14. .diskCache(new UnlimitedDiscCache(cacheDir)) // default
  15. .diskCacheSize(50 * 1024 * 1024)
  16. .diskCacheFileCount(100)
  17. .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
  18. .imageDownloader(new BaseImageDownloader(context)) // default
  19. .imageDecoder(new BaseImageDecoder()) // default
  20. .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
  21. .writeDebugLogs()
  22. .build();

上面的这些就是所有的选项配置,我们在项目中不需要每一个都自己设置,一般使用createDefault()创建的ImageLoaderConfiguration就能使用,然后调用ImageLoader的init()方法将ImageLoaderConfiguration参数传递进去,ImageLoader使用单例模式。

配置Android Manifest文件

[html] view
plain
 copy

  1. <manifest>
  2. <uses-permission android:name="android.permission.INTERNET" />
  3. <!-- Include next permission if you want to allow UIL to cache images on SD card -->
  4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  5. ...
  6. <application android:name="MyApplication">
  7. ...
  8. </application>
  9. </manifest>

接下来我们就可以来加载图片了,首先我们定义好Activity的布局文件

[html] view
plain
 copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <ImageView
  6. android:layout_gravity="center"
  7. android:id="@+id/image"
  8. android:src="@drawable/ic_empty"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content" />
  11. </FrameLayout>

里面只有一个ImageView,很简单,接下来我们就去加载图片,我们会发现ImageLader提供了几个图片加载的方法,主要是这几个displayImage(), loadImage(),loadImageSync(),loadImageSync()方法是同步的,android4.0有个特性,网络操作不能在主线程,所以loadImageSync()方法我们就不去使用

.

loadimage()加载图片

我们先使用ImageLoader的loadImage()方法来加载网络图片

[java] view
plain
 copy

  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {
  4. @Override
  5. public void onLoadingStarted(String imageUri, View view) {
  6. }
  7. @Override
  8. public void onLoadingFailed(String imageUri, View view,
  9. FailReason failReason) {
  10. }
  11. @Override
  12. public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
  13. mImageView.setImageBitmap(loadedImage);
  14. }
  15. @Override
  16. public void onLoadingCancelled(String imageUri, View view) {
  17. }
  18. });

传入图片的url和ImageLoaderListener, 在回调方法onLoadingComplete()中将loadedImage设置到ImageView上面就行了,如果你觉得传入ImageLoaderListener太复杂了,我们可以使用SimpleImageLoadingListener类,该类提供了ImageLoaderListener接口方法的空实现,使用的是缺省适配器模式

[java] view
plain
 copy

  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. ImageLoader.getInstance().loadImage(imageUrl, new SimpleImageLoadingListener(){
  4. @Override
  5. public void onLoadingComplete(String imageUri, View view,
  6. Bitmap loadedImage) {
  7. super.onLoadingComplete(imageUri, view, loadedImage);
  8. mImageView.setImageBitmap(loadedImage);
  9. }
  10. });

如果我们要指定图片的大小该怎么办呢,这也好办,初始化一个ImageSize对象,指定图片的宽和高,代码如下

[java] view
plain
 copy

  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. ImageSize mImageSize = new ImageSize(100, 100);
  4. ImageLoader.getInstance().loadImage(imageUrl, mImageSize, new SimpleImageLoadingListener(){
  5. @Override
  6. public void onLoadingComplete(String imageUri, View view,
  7. Bitmap loadedImage) {
  8. super.onLoadingComplete(imageUri, view, loadedImage);
  9. mImageView.setImageBitmap(loadedImage);
  10. }
  11. });

上面只是很简单的使用ImageLoader来加载网络图片,在实际的开发中,我们并不会这么使用,那我们平常会怎么使用呢?我们会用到DisplayImageOptions,他可以配置一些图片显示的选项,比如图片在加载中ImageView显示的图片,是否需要使用内存缓存,是否需要使用文件缓存等等,可供我们选择的配置如下

[java] view
plain
 copy

  1. DisplayImageOptions options = new DisplayImageOptions.Builder()
  2. .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
  3. .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
  4. .showImageOnFail(R.drawable.ic_error) // resource or drawable
  5. .resetViewBeforeLoading(false)  // default
  6. .delayBeforeLoading(1000)
  7. .cacheInMemory(false) // default
  8. .cacheOnDisk(false) // default
  9. .preProcessor(...)
  10. .postProcessor(...)
  11. .extraForDownloader(...)
  12. .considerExifParams(false) // default
  13. .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
  14. .bitmapConfig(Bitmap.Config.ARGB_8888) // default
  15. .decodingOptions(...)
  16. .displayer(new SimpleBitmapDisplayer()) // default
  17. .handler(new Handler()) // default
  18. .build();

我们将上面的代码稍微修改下

[java] view
plain
 copy

  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. ImageSize mImageSize = new ImageSize(100, 100);
  4. //显示图片的配置
  5. DisplayImageOptions options = new DisplayImageOptions.Builder()
  6. .showImageOnLoading(R.drawable.ic_stub)
  7. .showImageForEmptyUri(R.drawable.ic_empty)
  8. .showImageOnFail(R.drawable.ic_error)
  9. .cacheInMemory(true)
  10. .cacheOnDisk(true)
  11. .considerExifParams(true)
  12. .bitmapConfig(Bitmap.Config.RGB_565)
  13. .build();
  14. ImageLoader.getInstance().loadImage(imageUrl, mImageSize, options, new SimpleImageLoadingListener(){
  15. @Override
  16. public void onLoadingComplete(String imageUri, View view,
  17. Bitmap loadedImage) {
  18. super.onLoadingComplete(imageUri, view, loadedImage);
  19. mImageView.setImageBitmap(loadedImage);
  20. }
  21. });

我们使用了DisplayImageOptions来配置显示图片的一些选项,这里我添加了将图片缓存到内存中已经缓存图片到文件系统中,这样我们就不用担心每次都从网络中去加载图片了,是不是很方便呢,但是DisplayImageOptions选项中有些选项对于loadImage()方法是无效的,比如showImageOnLoading, showImageForEmptyUri等,

displayImage()加载图片

接下来我们就来看看网络图片加载的另一个方法displayImage(),代码如下

[java] view
plain
 copy

  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. //显示图片的配置
  4. DisplayImageOptions options = new DisplayImageOptions.Builder()
  5. .showImageOnLoading(R.drawable.ic_stub)
  6. .showImageOnFail(R.drawable.ic_error)
  7. .cacheInMemory(true)
  8. .cacheOnDisk(true)
  9. .bitmapConfig(Bitmap.Config.RGB_565)
  10. .build();
  11. ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);

从上面的代码中,我们可以看出,使用displayImage()比使用loadImage()方便很多,也不需要添加ImageLoadingListener接口,我们也不需要手动设置ImageView显示Bitmap对象,直接将ImageView作为参数传递到displayImage()中就行了,图片显示的配置选项中,我们添加了一个图片加载中ImageVIew上面显示的图片,以及图片加载出现错误显示的图片,效果如下,刚开始显示ic_stub图片,如果图片加载成功显示图片,加载产生错误显示ic_error

这个方法使用起来比较方便,而且使用displayImage()方法 他会根据控件的大小和imageScaleType来自动裁剪图片,我们修改下MyApplication,开启Log打印

[java] view
plain
 copy

  1. public class MyApplication extends Application {
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. //创建默认的ImageLoader配置参数
  6. ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
  7. .writeDebugLogs() //打印log信息
  8. .build();
  9. //Initialize ImageLoader with configuration.
  10. ImageLoader.getInstance().init(configuration);
  11. }
  12. }

我们来看下图片加载的Log信息

第一条信息中,告诉我们开始加载图片,打印出图片的url以及图片的最大宽度和高度,图片的宽高默认是设备的宽高,当然如果我们很清楚图片的大小,我们也可以去设置这个大小,在ImageLoaderConfiguration的选项中memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache)

第二条信息显示我们加载的图片来源于网络

第三条信息显示图片的原始大小为1024 x 682 经过裁剪变成了512 x 341

第四条显示图片加入到了内存缓存中,我这里没有加入到sd卡中,所以没有加入文件缓存的Log

我们在加载网络图片的时候,经常有需要显示图片下载进度的需求,Universal-Image-Loader当然也提供这样的功能,只需要在displayImage()方法中传入ImageLoadingProgressListener接口就行了,代码如下

[java] view
plain
 copy

  1. imageLoader.displayImage(imageUrl, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {
  2. @Override
  3. public void onProgressUpdate(String imageUri, View view, int current,
  4. int total) {
  5. }
  6. });

由于displayImage()方法中带ImageLoadingProgressListener参数的方法都有带ImageLoadingListener参数,所以我这里直接new 一个SimpleImageLoadingListener,然后我们就可以在回调方法onProgressUpdate()得到图片的加载进度。

加载其他来源的图片

使用Universal-Image-Loader框架不仅可以加载网络图片,还可以加载sd卡中的图片,Content provider等,使用也很简单,只是将图片的url稍加的改变下就行了,下面是加载文件系统的图片

[java] view
plain
 copy

  1. //显示图片的配置
  2. DisplayImageOptions options = new DisplayImageOptions.Builder()
  3. .showImageOnLoading(R.drawable.ic_stub)
  4. .showImageOnFail(R.drawable.ic_error)
  5. .cacheInMemory(true)
  6. .cacheOnDisk(true)
  7. .bitmapConfig(Bitmap.Config.RGB_565)
  8. .build();
  9. final ImageView mImageView = (ImageView) findViewById(R.id.image);
  10. String imagePath = "/mnt/sdcard/image.png";
  11. String imageUrl = Scheme.FILE.wrap(imagePath);
  12. //      String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
  13. imageLoader.displayImage(imageUrl, mImageView, options);

当然还有来源于Content provider,drawable,assets中,使用的时候也很简单,我们只需要给每个图片来源的地方加上Scheme包裹起来(Content provider除外),然后当做图片的url传递到imageLoader中,Universal-Image-Loader框架会根据不同的Scheme获取到输入流

[java] view
plain
 copy

  1. //图片来源于Content provider
  2. String contentprividerUrl = "content://media/external/audio/albumart/13";
  3. //图片来源于assets
  4. String assetsUrl = Scheme.ASSETS.wrap("image.png");
  5. //图片来源于
  6. String drawableUrl = Scheme.DRAWABLE.wrap("R.drawable.image");

GirdView,ListView加载图片

相信大部分人都是使用GridView,ListView来显示大量的图片,而当我们快速滑动GridView,ListView,我们希望能停止图片的加载,而在GridView,ListView停止滑动的时候加载当前界面的图片,这个框架当然也提供这个功能,使用起来也很简单,它提供了PauseOnScrollListener这个类来控制ListView,GridView滑动过程中停止去加载图片,该类使用的是代理模式

[java] view
plain
 copy

  1. listView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));
  2. gridView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));

第一个参数就是我们的图片加载对象ImageLoader, 第二个是控制是否在滑动过程中暂停加载图片,如果需要暂停传true就行了,第三个参数控制猛的滑动界面的时候图片是否加载

OutOfMemoryError

虽然这个框架有很好的缓存机制,有效的避免了OOM的产生,一般的情况下产生OOM的概率比较小,但是并不能保证OutOfMemoryError永远不发生,这个框架对于OutOfMemoryError做了简单的catch,保证我们的程序遇到OOM而不被crash掉,但是如果我们使用该框架经常发生OOM,我们应该怎么去改善呢?

  • 减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5
  • 在DisplayImageOptions选项中配置bitmapConfig为Bitmap.Config.RGB_565,因为默认是ARGB_8888, 使用RGB_565会比使用ARGB_8888少消耗2倍的内存
  • 在ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(new WeakMemoryCache()) 或者不使用内存缓存
  • 在DisplayImageOptions选项中设置.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(ImageScaleType.EXACTLY)

通过上面这些,相信大家对Universal-Image-Loader框架的使用已经非常的了解了,我们在使用该框架的时候尽量的使用displayImage()方法去加载图片,loadImage()是将图片对象回调到ImageLoadingListener接口的onLoadingComplete()方法中,需要我们手动去设置到ImageView上面,displayImage()方法中,对ImageView对象使用的是Weak references,方便垃圾回收器回收ImageView对象,如果我们要加载固定大小的图片的时候,使用loadImage()方法需要传递一个ImageSize对象,而displayImage()方法会根据ImageView对象的测量值,或者android:layout_width
and android:layout_height设定的值,或者android:maxWidth and/or android:maxHeight设定的值来裁剪图片

继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存,使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近期最少使用算法,我们可以给LruCache设定一个缓存图片的最大值,它会自动帮我们管理好缓存的图片总大小是否超过我们设定的值, 超过就删除近期最少使用的图片,而作为一个强大的图片加载框架,Universal-Image-Loader自然也提供了多种图片的缓存策略,下面就来详细的介绍下

内存缓存

首先我们来了解下什么是强引用和什么是弱引用?

强引用是指创建一个对象并把这个对象赋给一个引用变量, 强引用有引用变量指向时永远不会被垃圾回收。即使内存不足的时候宁愿报OOM也不被垃圾回收器回收,我们new的对象都是强引用

弱引用通过weakReference类来实现,它具有很强的不确定性,如果垃圾回收器扫描到有着WeakReference的对象,就会将其回收释放内存

现在我们来看Universal-Image-Loader有哪些内存缓存策略

1. 只使用的是强引用缓存

  • LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用,下面我会从源码上面分析这个类)

2.使用强引用和弱引用相结合的缓存有

  • UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
  • LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)
  • FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)
  • LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
  • LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)

3.只使用弱引用缓存

  • WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)

上面介绍了Universal-Image-Loader所提供的所有的内存缓存的类,当然我们也可以使用我们自己写的内存缓存类,我们还要看看要怎么将这些内存缓存加入到我们的项目中,我们只需要配置ImageLoaderConfiguration.memoryCache(...),如下

[java] view
plain
 copy

  1. ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
  2. .memoryCache(new WeakMemoryCache())
  3. .build();

下面我们来分析LruMemoryCache这个类的源代码

[java] view
plain
 copy

  1. package com.nostra13.universalimageloader.cache.memory.impl;
  2. import android.graphics.Bitmap;
  3. import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
  4. import java.util.Collection;
  5. import java.util.HashSet;
  6. import java.util.LinkedHashMap;
  7. import java.util.Map;
  8. /**
  9. * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
  10. * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
  11. * become eligible for garbage collection.<br />
  12. * <br />
  13. * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
  14. *
  15. * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
  16. * @since 1.8.1
  17. */
  18. public class LruMemoryCache implements MemoryCacheAware<String, Bitmap> {
  19. private final LinkedHashMap<String, Bitmap> map;
  20. private final int maxSize;
  21. /** Size of this cache in bytes */
  22. private int size;
  23. /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
  24. public LruMemoryCache(int maxSize) {
  25. if (maxSize <= 0) {
  26. throw new IllegalArgumentException("maxSize <= 0");
  27. }
  28. this.maxSize = maxSize;
  29. this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
  30. }
  31. /**
  32. * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
  33. * of the queue. This returns null if a Bitmap is not cached.
  34. */
  35. @Override
  36. public final Bitmap get(String key) {
  37. if (key == null) {
  38. throw new NullPointerException("key == null");
  39. }
  40. synchronized (this) {
  41. return map.get(key);
  42. }
  43. }
  44. /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */
  45. @Override
  46. public final boolean put(String key, Bitmap value) {
  47. if (key == null || value == null) {
  48. throw new NullPointerException("key == null || value == null");
  49. }
  50. synchronized (this) {
  51. size += sizeOf(key, value);
  52. Bitmap previous = map.put(key, value);
  53. if (previous != null) {
  54. size -= sizeOf(key, previous);
  55. }
  56. }
  57. trimToSize(maxSize);
  58. return true;
  59. }
  60. /**
  61. * Remove the eldest entries until the total of remaining entries is at or below the requested size.
  62. *
  63. * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
  64. */
  65. private void trimToSize(int maxSize) {
  66. while (true) {
  67. String key;
  68. Bitmap value;
  69. synchronized (this) {
  70. if (size < 0 || (map.isEmpty() && size != 0)) {
  71. throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
  72. }
  73. if (size <= maxSize || map.isEmpty()) {
  74. break;
  75. }
  76. Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
  77. if (toEvict == null) {
  78. break;
  79. }
  80. key = toEvict.getKey();
  81. value = toEvict.getValue();
  82. map.remove(key);
  83. size -= sizeOf(key, value);
  84. }
  85. }
  86. }
  87. /** Removes the entry for {@code key} if it exists. */
  88. @Override
  89. public final void remove(String key) {
  90. if (key == null) {
  91. throw new NullPointerException("key == null");
  92. }
  93. synchronized (this) {
  94. Bitmap previous = map.remove(key);
  95. if (previous != null) {
  96. size -= sizeOf(key, previous);
  97. }
  98. }
  99. }
  100. @Override
  101. public Collection<String> keys() {
  102. synchronized (this) {
  103. return new HashSet<String>(map.keySet());
  104. }
  105. }
  106. @Override
  107. public void clear() {
  108. trimToSize(-1); // -1 will evict 0-sized elements
  109. }
  110. /**
  111. * Returns the size {@code Bitmap} in bytes.
  112. * <p/>
  113. * An entry‘s size must not change while it is in the cache.
  114. */
  115. private int sizeOf(String key, Bitmap value) {
  116. return value.getRowBytes() * value.getHeight();
  117. }
  118. @Override
  119. public synchronized final String toString() {
  120. return String.format("LruCache[maxSize=%d]", maxSize);
  121. }
  122. }

我们可以看到这个类中维护的是一个LinkedHashMap,在LruMemoryCache构造函数中我们可以看到,我们为其设置了一个缓存图片的最大值maxSize,并实例化LinkedHashMap, 而从LinkedHashMap构造函数的第三个参数为ture,表示它是按照访问顺序进行排序的,

我们来看将bitmap加入到LruMemoryCache的方法put(String key, Bitmap value),  第61行,sizeOf()是计算每张图片所占的byte数,size是记录当前缓存bitmap的总大小,如果该key之前就缓存了bitmap,我们需要将之前的bitmap减掉去,接下来看trimToSize()方法,我们直接看86行,如果当前缓存的bitmap总数小于设定值maxSize,不做任何处理,如果当前缓存的bitmap总数大于maxSize,删除LinkedHashMap中的第一个元素,size中减去该bitmap对应的byte数

我们可以看到该缓存类比较简单,逻辑也比较清晰,如果大家想知道其他内存缓存的逻辑,可以去分析分析其源码,在这里我简单说下FIFOLimitedMemoryCache的实现逻辑,该类使用的HashMap来缓存bitmap的弱引用,然后使用LinkedList来保存成功加入到FIFOLimitedMemoryCache的bitmap的强引用,如果加入的FIFOLimitedMemoryCache的bitmap总数超过限定值,直接删除LinkedList的第一个元素,所以就实现了先进先出的缓存策略,其他的缓存都类似,有兴趣的可以去看看。

硬盘缓存

在新版本中,使用LruDiscCache代替LimitedAgeDiscCache,TotalSizeLimitedDiscCache,FileCountLimitedDiscCache

下面我们就来分析分析BaseDiscCache的源码实现

/*******************************************************************************
 * Copyright 2011-2014 Sergey Tarasevich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.disc.impl;

import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import com.nostra13.universalimageloader.utils.IoUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Base disk cache.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @see FileNameGenerator
 * @since 1.0.0
 */
public abstract class BaseDiskCache implements DiskCache {
   /** {@value */
   public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
   /** {@value */
   public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
   /** {@value */
   public static final int DEFAULT_COMPRESS_QUALITY = 100;

   private static final String ERROR_ARG_NULL = " argument must be not null";
   private static final String TEMP_IMAGE_POSTFIX = ".tmp";

   protected final File cacheDir;
   protected final File reserveCacheDir;

   protected final FileNameGenerator fileNameGenerator;

   protected int bufferSize = DEFAULT_BUFFER_SIZE;

   protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
   protected int compressQuality = DEFAULT_COMPRESS_QUALITY;

   /** @param cacheDir Directory for file caching */
   public BaseDiskCache(File cacheDir) {
      this(cacheDir, null);
   }

   /**
    * @param cacheDir        Directory for file caching
    * @param reserveCacheDir null-ok; Reserve directory for file caching. It‘s used when the primary directory isn‘t available.
    */
   public BaseDiskCache(File cacheDir, File reserveCacheDir) {
      this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator());
   }

   /**
    * @param cacheDir          Directory for file caching
    * @param reserveCacheDir   null-ok; Reserve directory for file caching. It‘s used when the primary directory isn‘t available.
    * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
    *                          Name generator} for cached files
    */
   public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
      if (cacheDir == null) {
         throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
      }
      if (fileNameGenerator == null) {
         throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
      }

      this.cacheDir = cacheDir;
      this.reserveCacheDir = reserveCacheDir;
      this.fileNameGenerator = fileNameGenerator;
   }

   @Override
   public File getDirectory() {
      return cacheDir;
   }

   @Override
   public File get(String imageUri) {
      return getFile(imageUri);
   }

   @Override
   public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
      File imageFile = getFile(imageUri);
      File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
      boolean loaded = false;
      try {
         OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
         try {
            loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
         } finally {
            IoUtils.closeSilently(os);
         }
      } finally {
         if (loaded && !tmpFile.renameTo(imageFile)) {
            loaded = false;
         }
         if (!loaded) {
            tmpFile.delete();
         }
      }
      return loaded;
   }

   @Override
   public boolean save(String imageUri, Bitmap bitmap) throws IOException {
      File imageFile = getFile(imageUri);
      File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
      OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
      boolean savedSuccessfully = false;
      try {
         savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
      } finally {
         IoUtils.closeSilently(os);
         if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
            savedSuccessfully = false;
         }
         if (!savedSuccessfully) {
            tmpFile.delete();
         }
      }
      bitmap.recycle();
      return savedSuccessfully;
   }

   @Override
   public boolean remove(String imageUri) {
      return getFile(imageUri).delete();
   }

   @Override
   public void close() {
      // Nothing to do
   }

   @Override
   public void clear() {
      File[] files = cacheDir.listFiles();
      if (files != null) {
         for (File f : files) {
            f.delete();
         }
      }
   }

   /** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
   protected File getFile(String imageUri) {
      String fileName = fileNameGenerator.generate(imageUri);
      File dir = cacheDir;
      if (!cacheDir.exists() && !cacheDir.mkdirs()) {
         if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
            dir = reserveCacheDir;
         }
      }
      return new File(dir, fileName);
   }

   public void setBufferSize(int bufferSize) {
      this.bufferSize = bufferSize;
   }

   public void setCompressFormat(Bitmap.CompressFormat compressFormat) {
      this.compressFormat = compressFormat;
   }

   public void setCompressQuality(int compressQuality) {
      this.compressQuality = compressQuality;
   }
}

下面主要从源码的角度上面去解读这个强大的图片加载框架,

[java] view
plain
 copy

  1. ImageView mImageView = (ImageView) findViewById(R.id.image);
  2. String imageUrl = "http://pic36.nipic.com/20131217/6704106_233034463381_2.jpg";
  3. //显示图片的配置
  4. DisplayImageOptions options = new DisplayImageOptions.Builder()
  5. .showImageOnLoading(R.drawable.ic_stub)
  6. .showImageOnFail(R.drawable.ic_error)
  7. .cacheInMemory(true)
  8. .cacheOnDisk(true)
  9. .bitmapConfig(Bitmap.Config.RGB_565)
  10. .build();
  11. ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);

大部分的时候我们都是使用上面的代码去加载图片,我们先看下

[java] view
plain
 copy

  1. public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
  2. displayImage(uri, new ImageViewAware(imageView), options, null, null);
  3. }

从上面的代码中,我们可以看出,它会将ImageView转换成ImageViewAware, ImageViewAware主要是做什么的呢?该类主要是将ImageView进行一个包装,将ImageView的强引用变成弱引用,当内存不足的时候,可以更好的回收ImageView对象,还有就是获取ImageView的宽度和高度。这使得我们可以根据ImageView的宽高去对图片进行一个裁剪,减少内存的使用。

接下来看具体的displayImage方法啦,由于这个方法代码量蛮多的,所以这里我分开来读

[java] view
plain
 copy

  1. checkConfiguration();
  2. if (imageAware == null) {
  3. throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
  4. }
  5. if (listener == null) {
  6. listener = emptyListener;
  7. }
  8. if (options == null) {
  9. options = configuration.defaultDisplayImageOptions;
  10. }
  11. if (TextUtils.isEmpty(uri)) {
  12. engine.cancelDisplayTaskFor(imageAware);
  13. listener.onLoadingStarted(uri, imageAware.getWrappedView());
  14. if (options.shouldShowImageForEmptyUri()) {
  15. imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
  16. } else {
  17. imageAware.setImageDrawable(null);
  18. }
  19. listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
  20. return;
  21. }

第1行代码是检查ImageLoaderConfiguration是否初始化,这个初始化是在Application中进行的

12-21行主要是针对url为空的时候做的处理,第13行代码中,ImageLoaderEngine中存在一个HashMap,用来记录正在加载的任务,加载图片的时候会将ImageView的id和图片的url加上尺寸加入到HashMap中,加载完成之后会将其移除,然后将DisplayImageOptions的imageResForEmptyUri的图片设置给ImageView,最后回调给ImageLoadingListener接口告诉它这次任务完成了。

[java] view
plain
 copy

  1. ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
  2. String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
  3. engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
  4. listener.onLoadingStarted(uri, imageAware.getWrappedView());
  5. Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
  6. if (bmp != null && !bmp.isRecycled()) {
  7. L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
  8. if (options.shouldPostProcess()) {
  9. ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
  10. options, listener, progressListener, engine.getLockForUri(uri));
  11. ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
  12. defineHandler(options));
  13. if (options.isSyncLoading()) {
  14. displayTask.run();
  15. } else {
  16. engine.submit(displayTask);
  17. }
  18. } else {
  19. options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
  20. listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
  21. }
  22. }

第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接口

[java] view
plain
 copy

  1. if (options.shouldShowImageOnLoading()) {
  2. imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
  3. } else if (options.isResetViewBeforeLoading()) {
  4. imageAware.setImageDrawable(null);
  5. }
  6. ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
  7. options, listener, progressListener, engine.getLockForUri(uri));
  8. LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
  9. defineHandler(options));
  10. if (options.isSyncLoading()) {
  11. displayTask.run();
  12. } else {
  13. engine.submit(displayTask);
  14. }

这段代码主要是Bitmap不在内存缓存,从文件中或者网络里面获取bitmap对象,实例化一个LoadAndDisplayImageTask对象,LoadAndDisplayImageTask实现了Runnable,如果配置了isSyncLoading为true, 直接执行LoadAndDisplayImageTask的run方法,表示同步,默认是false,将LoadAndDisplayImageTask提交给线程池对象

接下来我们就看LoadAndDisplayImageTask的run(), 这个类还是蛮复杂的,我们还是一段一段的分析

[java] view
plain
 copy

  1. if (waitIfPaused()) return;
  2. if (delayIfNeed()) return;

如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑, 接下来我们先看看waitIfPaused()

[java] view
plain
 copy

  1. private boolean waitIfPaused() {
  2. AtomicBoolean pause = engine.getPause();
  3. if (pause.get()) {
  4. synchronized (engine.getPauseLock()) {
  5. if (pause.get()) {
  6. L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
  7. try {
  8. engine.getPauseLock().wait();
  9. } catch (InterruptedException e) {
  10. L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
  11. return true;
  12. }
  13. L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
  14. }
  15. }
  16. }
  17. return isTaskNotActual();
  18. }

这个方法是干嘛用呢,主要是我们在使用ListView,GridView去加载图片的时候,有时候为了滑动更加的流畅,我们会选择手指在滑动或者猛地一滑动的时候不去加载图片,所以才提出了这么一个方法,那么要怎么用呢?  这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling
控制猛的滑动ListView,GridView是否停止加载图片

除此之外,这个方法的返回值由isTaskNotActual()决定,我们接着看看isTaskNotActual()的源码

[java] view
plain
 copy

  1. private boolean isTaskNotActual() {
  2. return isViewCollected() || isViewReused();
  3. }

isViewCollected()是判断我们ImageView是否被垃圾回收器回收了,如果回收了,LoadAndDisplayImageTask方法的run()就直接返回了,isViewReused()判断该ImageView是否被重用,被重用run()方法也直接返回,为什么要用isViewReused()方法呢?主要是ListView,GridView我们会复用item对象,假如我们先去加载ListView,GridView第一页的图片的时候,第一页图片还没有全部加载完我们就快速的滚动,isViewReused()方法就会避免这些不可见的item去加载图片,而直接加载当前界面的图片

[java] view
plain
 copy

  1. ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
  2. L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
  3. if (loadFromUriLock.isLocked()) {
  4. L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
  5. }
  6. loadFromUriLock.lock();
  7. Bitmap bmp;
  8. try {
  9. checkTaskNotActual();
  10. bmp = configuration.memoryCache.get(memoryCacheKey);
  11. if (bmp == null || bmp.isRecycled()) {
  12. bmp = tryLoadBitmap();
  13. if (bmp == null) return; // listener callback already was fired
  14. checkTaskNotActual();
  15. checkTaskInterrupted();
  16. if (options.shouldPreProcess()) {
  17. L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
  18. bmp = options.getPreProcessor().process(bmp);
  19. if (bmp == null) {
  20. L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
  21. }
  22. }
  23. if (bmp != null && options.isCacheInMemory()) {
  24. L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
  25. configuration.memoryCache.put(memoryCacheKey, bmp);
  26. }
  27. } else {
  28. loadedFrom = LoadedFrom.MEMORY_CACHE;
  29. L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
  30. }
  31. if (bmp != null && options.shouldPostProcess()) {
  32. L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
  33. bmp = options.getPostProcessor().process(bmp);
  34. if (bmp == null) {
  35. L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
  36. }
  37. }
  38. checkTaskNotActual();
  39. checkTaskInterrupted();
  40. } catch (TaskCancelledException e) {
  41. fireCancelEvent();
  42. return;
  43. } finally {
  44. loadFromUriLock.unlock();
  45. }

第1行代码有一个loadFromUriLock,这个是一个锁,获取锁的方法在ImageLoaderEngine类的getLockForUri()方法中

[java] view
plain
 copy

  1. ReentrantLock getLockForUri(String uri) {
  2. ReentrantLock lock = uriLocks.get(uri);
  3. if (lock == null) {
  4. lock = new ReentrantLock();
  5. uriLocks.put(uri, lock);
  6. }
  7. return lock;
  8. }

从上面可以看出,这个锁对象与图片的url是相互对应的,为什么要这么做?也行你还有点不理解,不知道大家有没有考虑过一个场景,假如在一个ListView中,某个item正在获取图片的过程中,而此时我们将这个item滚出界面之后又将其滚进来,滚进来之后如果没有加锁,该item又会去加载一次图片,假设在很短的时间内滚动很频繁,那么就会出现多次去网络上面请求图片,所以这里根据图片的Url去对应一个ReentrantLock对象,让具有相同Url的请求就会在第7行等待,等到这次图片加载完成之后,ReentrantLock就被释放,刚刚那些相同Url的请求就会继续执行第7行下面的代码

来到第12行,它们会先从内存缓存中获取一遍,如果内存缓存中没有在去执行下面的逻辑,所以ReentrantLock的作用就是避免这种情况下重复的去从网络上面请求图片。

第14行的方法tryLoadBitmap(),这个方法确实也有点长,我先告诉大家,这里面的逻辑是先从文件缓存中获取有没有Bitmap对象,如果没有在去从网络中获取,然后将bitmap保存在文件系统中,我们还是具体分析下

[java] view
plain
 copy

  1. File imageFile = configuration.diskCache.get(uri);
  2. if (imageFile != null && imageFile.exists()) {
  3. L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
  4. loadedFrom = LoadedFrom.DISC_CACHE;
  5. checkTaskNotActual();
  6. bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
  7. }

先判断文件缓存中有没有该文件,如果有的话,直接去调用decodeImage()方法去解码图片,该方法里面调用BaseImageDecoder类的decode()方法,根据ImageView的宽高,ScaleType去裁剪图片,具体的代码我就不介绍了,大家自己去看看,我们接下往下看tryLoadBitmap()方法

[java] view
plain
 copy

  1. if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
  2. L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
  3. loadedFrom = LoadedFrom.NETWORK;
  4. String imageUriForDecoding = uri;
  5. if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
  6. imageFile = configuration.diskCache.get(uri);
  7. if (imageFile != null) {
  8. imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
  9. }
  10. }
  11. checkTaskNotActual();
  12. bitmap = decodeImage(imageUriForDecoding);
  13. if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
  14. fireFailEvent(FailType.DECODING_ERROR, null);
  15. }
  16. }

第1行表示从文件缓存中获取的Bitmap为null,或者宽高为0,就去网络上面获取Bitmap,来到第6行代码是否配置了DisplayImageOptions的isCacheOnDisk,表示是否需要将Bitmap对象保存在文件系统中,一般我们需要配置为true, 默认是false这个要注意下,然后就是执行tryCacheImageOnDisk()方法,去服务器上面拉取图片并保存在本地文件中

[java] view
plain
 copy

  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. }
  7. /** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */
  8. private boolean tryCacheImageOnDisk() throws TaskCancelledException {
  9. L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
  10. boolean loaded;
  11. try {
  12. loaded = downloadImage();
  13. if (loaded) {
  14. int width = configuration.maxImageWidthForDiskCache;
  15. int height = configuration.maxImageHeightForDiskCache;
  16. if (width > 0 || height > 0) {
  17. L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
  18. resizeAndSaveImage(width, height); // TODO : process boolean result
  19. }
  20. }
  21. } catch (IOException e) {
  22. L.e(e);
  23. loaded = false;
  24. }
  25. return loaded;
  26. }
  27. private boolean downloadImage() throws IOException {
  28. InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
  29. return configuration.diskCache.save(uri, is, this);
  30. }

第6行的downloadImage()方法是负责下载图片,并将其保持到文件缓存中,将下载保存Bitmap的进度回调到IoUtils.CopyListener接口的onBytesCopied(int current, int total)方法中,所以我们可以设置ImageLoadingProgressListener接口来获取图片下载保存的进度,这里保存在文件系统中的图片是原图

第16-17行,获取ImageLoaderConfiguration是否设置保存在文件系统中的图片大小,如果设置了maxImageWidthForDiskCache和maxImageHeightForDiskCache,会调用resizeAndSaveImage()方法对图片进行裁剪然后在替换之前的原图,保存裁剪后的图片到文件系统的,之前有同学问过我说这个框架保存在文件系统的图片都是原图,怎么才能保存缩略图,只要在Application中实例化ImageLoaderConfiguration的时候设置maxImageWidthForDiskCache和maxImageHeightForDiskCache就行了

[java] view
plain
 copy

  1. if (bmp == null) return; // listener callback already was fired
  2. checkTaskNotActual();
  3. checkTaskInterrupted();
  4. if (options.shouldPreProcess()) {
  5. L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
  6. bmp = options.getPreProcessor().process(bmp);
  7. if (bmp == null) {
  8. L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
  9. }
  10. }
  11. if (bmp != null && options.isCacheInMemory()) {
  12. L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
  13. configuration.memoryCache.put(memoryCacheKey, bmp);
  14. }

接下来这里就简单了,6-12行是否要对Bitmap进行处理,这个需要自行实现,14-17就是将图片保存到内存缓存中去

[java] view
plain
 copy

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

最后这两行代码就是一个显示任务,直接看DisplayBitmapTask类的run()方法

[java] view
plain
 copy

  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. }

假如ImageView被回收了或者被重用了,回调给ImageLoadingListener接口,否则就调用BitmapDisplayer去显示Bitmap

文章写到这里就已经写完了,不知道大家对这个开源框架有没有进一步的理解,这个开源框架设计也很灵活,用了很多的设计模式,比如建造者模式,装饰模式,代理模式,策略模式等等,这样方便我们去扩展,实现我们想要的功能,今天的讲解就到这了,有对这个框架不明白的地方可以在下面留言,我会尽量为大家解答的。

时间: 2025-01-03 14:33:00

Universal-Image-Loader完全解析的相关文章

【Android应用开发】 Universal Image Loader ( 使用简介 | 示例代码解析 )

作者 : 韩曙亮 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/50824912 相关地址介绍 : -- Universal Image Loader 项目 GitHub 官方地址 : https://github.com/nostra13/Android-Universal-Image-Loader . -- Universal Image Loader 项目完整中文注释版, 已经将所有类都进行了中文注释, 适合源码学习参

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的源码分析

项目地址:https://github.com/nostra13/android-universal-image-loader 1. 功能介绍 1.1 Android Universal Image Loader Android Universal Image Loader 是一个强大的.可高度定制的图片缓存,本文简称为UIL. 简单的说 UIL 就做了一件事--获取图片并显示在相应的控件上. 1.2 基本使用 1.2.1 初始化 添加完依赖后在Application或Activity中初始化I

开源项目Universal Image Loader for Android 说明文档 (1) 简介

 When developing applications for Android, one often facesthe problem of displaying some graphical content from the Internet. So, youshould provide image loading from the Web in an Android app, their processingand displaying with limited memory aga

【译】UNIVERSAL IMAGE LOADER. PART 3(四个DisplayImage重载方法详解)

在之前的文章,我们重点讲了Android-Universal-Image-Loader的三个主要组件,现在我们终于可以开始使用它了. Android-Universal-Image-Loader有四个重载方法 void displayImage(String url, ImageView view) void displayImage(String url, ImageView view, DisplayImageOptions options) void displayImage(String

【译】UNIVERSAL IMAGE LOADER.PART 2---ImageLoaderConfiguration详解

ImageLoader类中包含了所有操作.他是一个单例,为了获取它的一个单一实例,你需要调用getInstance()方法.在使用ImageLoader来显示图片之前,你需要初始化它的配置-ImageLoaderConfiguration使用init(…)方法.然后,你就可以使用可以明确地根据需要使用不同形式的displayImage(…). 总之,ImageLoader最简单的用法如下所示(使用默认配置): ImageView imageView = ... // view, where th

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

开源项目Universal Image Loader for Android 说明文档 (1) 简单介绍

 When developing applications for Android, one often facesthe problem of displaying some graphical content from the Internet. So, youshould provide image loading from the Web in an Android app, their processingand displaying with limited memory aga

universal image loader在listview/gridview中滚动时重复加载图片的问题及解决方法

在listview/gridview中使用UIL来display每个item的图片,当图片数量较多需要滑动滚动时会出现卡顿,而且加载过的图片再次上翻后依然会重复加载(显示设置好的加载中图片) 最近在使用UIL遇到了这个问题,相信这个问题许多使用UIL的人都碰到过 现在把解决方法贴出来给有同样问题的朋友做参考 先看下UIL的工作流程 在已经允许内存,存储卡缓存的前提下,当一个图片被请求display时,首先要判断图片是否缓存在内存中,如果false则尝试从存储卡读取,如果依然不存在最后才从网络地址

Android 使用Universal Image Loader绘制带圆角的图片(一)

Android 使用Universal Image Loader绘制带圆角的图片(一) 绘制带圆角的控件难吗?貌似不难.对于一个普通layout或者widget,要绘制圆角,只要把 background设置成下面这样的drawable就行了. <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/and