Universal-Image-Loader图片加载框架

引言

在前面的安卓面试系列–OOM异常(二)中我们已经给大家简单分析了一下Universal-Image-Loader,并且还在文末给大家提供了一个已经封装好的工具类,不知道大家觉得好不好用呢?今天我们就来分析一下安卓中几大主流的图片加载框架的优缺点。

Universal-Image-Loader

我们先来回顾一下这个图片加载框架,主要是要进行两个配置,一个是图片下载前的配置,还有一个是图片显示配置,我们一个一个的来说。

1、导包

方式一:

compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' 

方式二:

导入jar包到libs目录下,jar包自行百度,这里就不提供了

2、权限配置

<!--  如果想要加载网络图片就需要下面这个权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
<!--  如果想要加载SD卡上的图片就需要下面这个权限 --> 
    <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE" />

3、图片下载配置ImageLoaderConfiguration

a、使用默认配置:

ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);  

b、自己配置参数:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)  
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions 内存缓存文件的最大长宽  
        .diskCacheExtraOptions(480, 800, null)  // 本地缓存的详细信息(缓存的最大长宽),最好不要设置这个   
        .taskExecutor(...)  
        .taskExecutorForCachedImages(...)  
        .threadPoolSize(3) // default  线程池内加载的数量  
        .threadPriority(Thread.NORM_PRIORITY - 2) // default 设置当前线程的优先级  
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default  
        .denyCacheImageMultipleSizesInMemory()  
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通过自己的内存缓存实现  
        .memoryCacheSize(2 * 1024 * 1024)  // 内存缓存的最大值  
        .memoryCacheSizePercentage(13) // default  
        .diskCache(new UnlimitedDiscCache(cacheDir)) // default 可以自定义缓存路径    
        .diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)缓存的最大值  
        .diskCacheFileCount(100)  // 可以缓存的文件数量   
        // default为使用HASHCODE对UIL进行加密命名, 还可以用MD5(new Md5FileNameGenerator())加密  
        .diskCacheFileNameGenerator(new HashCodeFileNameGenerator())   
        .imageDownloader(new BaseImageDownloader(context)) // default  
        .imageDecoder(new BaseImageDecoder()) // default  
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default  
        .writeDebugLogs() // 打印debug log  
        .build(); //开始构建  

配置好ImageLoaderConfiguration,一定不要忘记进行初始化操作(一般在application中进行初始化)

ImageLoader.getInstance().init(config);

注:上面的配置请根据自己的需要进行配置,不是所有的都要进行配置的

4、图片显示配置

a、首先要得到ImageLoader的实例(使用的单例模式)

ImageLoader imageLoader = ImageLoader.getInstance();  

b、相关显示参数设置

DisplayImageOptions options = new DisplayImageOptions.Builder()  
        .showImageOnLoading(R.drawable.ic_stub) // 设置图片下载期间显示的图片  
        .showImageForEmptyUri(R.drawable.ic_empty) // 设置图片Uri为空或是错误的时候显示的图片  
        .showImageOnFail(R.drawable.ic_error) // 设置图片加载或解码过程中发生错误显示的图片  
        .resetViewBeforeLoading(false)  // default 设置图片在加载前是否重置、复位  
        .delayBeforeLoading(1000)  // 下载前的延迟时间  
        .cacheInMemory(false) // default  设置下载的图片是否缓存在内存中  
        .cacheOnDisk(false) // default  设置下载的图片是否缓存在SD卡中  
        .preProcessor(...)  
        .postProcessor(...)  
        .extraForDownloader(...)  
        .considerExifParams(false) // default  
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片缩放方式  
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default 设置图片的解码类型  
        .decodingOptions(...)  // 图片的解码设置  
        .displayer(new SimpleBitmapDisplayer()) // default  还可以设置圆角图片new RoundedBitmapDisplayer(20)  
        .handler(new Handler()) // default  
        .build();

注:如果DisplayImageOption没有传递给ImageLoader.displayImage(…)方法,那么默认配置显示选项
(ImageLoaderConfiguration.defaultDisplayImageOptions(…))将被使用。

1).imageScaleType(ImageScaleType imageScaleType)  //设置图片的缩放方式  
      缩放类型mageScaleType:  
          EXACTLY :图像将完全按比例缩小的目标大小  
          EXACTLY_STRETCHED:图片会缩放到目标大小完全  
          IN_SAMPLE_INT:图像将被二次采样的整数倍  
          IN_SAMPLE_POWER_OF_2:图片将降低2倍,直到下一减少步骤,使图像更小的目标大小    
          NONE:图片不会调整  
  2).displayer(BitmapDisplayer displayer)   //设置图片的显示方式  
      显示方式displayer:  
         RoundedBitmapDisplayer(int roundPixels)设置圆角图片  
         FakeBitmapDisplayer()这个类什么都没做  
         FadeInBitmapDisplayer(int durationMillis)设置图片渐显的时间  
         SimpleBitmapDisplayer()正常显示一张图片  

c、显示图片:

1、  ImageLoader.getInstance().displayImage(uri, imageView);  
2、  ImageLoader.getInstance().displayImage(uri, imageView, options);  
3、  ImageLoader.getInstance().displayImage(uri, imageView, listener);  
4、  ImageLoader.getInstance().displayImage(uri, imageView, options, listener);  
5、  ImageLoader.getInstance().displayImage(uri, imageView, options, listener, progressListener);  

其中:
imageUrl 图片的URL地址
imageView 显示图片的ImageView控件
options DisplayImageOptions配置信息
listener 图片下载情况的监听
progressListener 图片下载进度的监听

  • 方法1:最简单的方式,我们只需要定义要显示的图片的URL和要显示图片的ImageView。这种情况下,图片的显示选项会使用默认的配置
  • 方法2:加载自定义配置的一个图片
  • 方法3:加载带监听的一个图片
  • 方法4:加载自定义配置且带监听的一个图片

参数最全的方法:


ImageLoader.getInstance().displayImage(uri, imageView, options,  
        new ImageLoadingListener() {  

            @Override  
            public void onLoadingStarted(String arg0, View arg1) {  
                //开始加载  
            }  

            @Override  
            public void onLoadingFailed(String arg0, View arg1,  
                    FailReason arg2) {  
                //加载失败  
            }  

            @Override  
            public void onLoadingComplete(String arg0, View arg1,  
                    Bitmap arg2) {  
                //加载成功  
            }  

            @Override  
            public void onLoadingCancelled(String arg0, View arg1) {  
                //加载取消  
            }  
        }, new ImageLoadingProgressListener() {  

            @Override  
            public void onProgressUpdate(String imageUri, View view,  int current, int total) {  
                //加载进度  
            }  
        }); 

5、注意事项:

a、如果你的程序经常出现OOM,你可以尝试以下设置:

  • 禁用在内存中缓存cacheInMemory(false);
  • 减少配置的线程池的大小(.threadPoolSize(…)),建议1~5;
  • 在显示选项中使用 .bitmapConfig(Bitmap.Config.RGB_565) . RGB_565模式消耗的内存比ARGB_8888模式少两倍;
  • 配置中使用.diskCacheExtraOptions(480, 320, null);
  • 配置中使用 .memoryCache(newWeakMemoryCache()) 或者完全禁用在内存中缓存(don’t call .cacheInMemory());
  • 在显示选项中使用.imageScaleType(ImageScaleType.EXACTLY) 或 .imageScaleType(ImageScaleType.IN_SAMPLE_INT);

b、一定要对ImageLoaderConfiguration进行初始化,否则会报错;

c、缓存到Sd卡需要在AndroidManifest.xml文件中进行如下配置:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 

d、内存缓存可以使用以下已经已实现的方法(ImageLoaderConfiguration.memoryCache(…)):

  • 只使用强引用:
LruMemoryCache (缓存大小超过指定值时,删除最近最少使用的bitmap)  --默认情况下使用
  • 缓存使用弱引用和强引用:
UsingFreqLimitedMemoryCache (缓存大小超过指定值时,删除最少使的bitmap)  
LRULimitedMemoryCache (缓存大小超过指定值时,删除最近最少使用的<span style="font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif;">bitmap) --默认值</span>  
FIFOLimitedMemoryCache (缓存大小超过指定值时,按先进先出规则删除的<span style="font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif;">bitmap)</span>  
LargestLimitedMemoryCache (缓存大小超过指定值时,删除最大的bitmap)  
LimitedAgeMemoryCache (缓存对象超过定义的时间后删除)  
  • 只使用弱引用:
WeakMemoryCache(没有限制缓存)

e、硬盘缓存可以使用以下已经实现的方式(ImageLoaderConfiguration.diskCache(…))

UnlimitedDiskCache   不限制缓存大小(默认)  
TotalSizeLimitedDiskCache (设置总缓存大小,超过时删除最久之前的缓存)  
FileCountLimitedDiskCache (设置总缓存文件数量,当到达警戒值时,删除最久之前的缓存。如果文件的大小都一样的时候,可以使用该模式)  
LimitedAgeDiskCache (不限制缓存大小,但是设置缓存时间,到期后删除)  

6、总结

  • 好了,到这里我们已经对UIL这个框架的基本使用的介绍已经结束了,简单总结一下,使用这个框架首先需要导包,配置权限,配置图片下载前的各项参数,配置图片显示的各项参数,然后就是通过ImageLoader实例对象的displayImage()方法展示图片。
  • 最简单的就是传入一个url和一个ImageView控件,当然也可以添加自定义图片显示配置options,图片下载情况监听listener,主要监听图片下载开始、完成、失败、取消四种状态,图片下载进度监听progressListener。
  • 然后我们还提供了几种内存缓存和硬盘缓存策略,先说内存缓存,默认的就是LruMemoryCache(强引用),当内存满了以后,删除最近最少使用的图片,还有一些其他的删除规则,比如时间先后,文件大小等等。再说硬盘缓存,跟内存缓存类似,也可以按照时间先后来删除,还可以设定缓存时间,到期删除。

你以为UIL到这里就结束了?作为一个有追求的程序员,怎么可以不看源码。

UIL图片框架缓存策略

关于UIL的三级缓存大家可以去看看我的这篇博客安卓面试系列–OOM异常(二),里面介绍了内存缓存、硬盘缓存以及网络下载三种缓存方式的调用顺序,这里就不多赘述了。在这里我想给大家讲一讲UIL框架中最著名的LruCache(最近最少使用)算法是怎么实现的。

点开UIL框架的源码,我们可以看到这样的一个目录结构:

这里写图片描述

其中:memory表示内存缓存目录,disk表示硬盘缓存目录

Lru缓存策略

我们先来讲一下内存缓存,首先分析一下LruMemoryCache这个类,这个类实现了一个接口MemoryCache,所以我们先来看下这个接口里面有什么方法:

/**
* 内存缓存的接口
*/

public interface MemoryCache {

    /**
    * 根据键把图片加入内存
    */
    boolean put(String key, Bitmap value);

    /**
    * 根据键从内存中取出图片
    */
    Bitmap get(String key);

    /**
    * 根据键从内存中移除图片
    */
    Bitmap remove(String key);

    /**
    * 返回所有的键
    */
    Collection<String> keys();

    /**
    * 清空所有数据
    */
    void clear();
}
/**
* LRU 缓存策略 Least Recentlty Use 最近最少使用
*/

public class LruMemoryCache implements MemoryCache{

    /**
    *  链表数据结构存储
    */
    private final LinkedHashMap<String, Bitmap> map;

    /**
    * 最大缓存大小
    */
    private final int maxSize;

    /**
    * 当前使用的缓存大小
    */
    private int size;

    /**
    * 构造方法初始化缓存类
    */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);//ture,表示它是按照访问顺序进行排序的
    }

    /**
    * 取出当前key对应的图片,并把这张图片放在list的尾部
    */
    @Override
    public final Bitmap get(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            return map.get(key);
        }
    }

    /**
    * 添加图片,添加至末尾
    */
    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {
            //加入新图片的大小
            size += sizeOf(key, value);

            // 图片已经存在 ,替换原来的图片,
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                //移除旧图片大小
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

    /**
    * 超过maxSize,不停的移除图片,直到小于maxSize
    */
    private void trimToSize(int maxSize) {
        // 循环判断当前缓存大小
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                // 链表结构最少使用的数据项
                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

    /**
    * 移除图片
    */
    @Override
    public final Bitmap remove(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            Bitmap previous = map.remove(key);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }

    @Override
    public Collection<String> keys() {
        synchronized (this) {
            return new HashSet<String>(map.keySet());
        }
    }

    /**
    * 清空数据
    */
    @Override
    public void clear() {
        trimToSize(-1);
    }

    /**
    * 计算Bitmap的大小
    */
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public synchronized final String toString() {
        return String.format("LruCache[maxSize=%d]", maxSize);
    }
}
总结
  • 以上就是内存缓存最近最少使用算法的实现,总结一下,把一张图片加入到内存缓存中首先要判断键和值是否为空,如果为空就抛出异常,都不为空的情况下,我们使用一个同步块,先计算一下新加入内存的图片有多大,然后把它加入内存,再判断一下这张图片是否已经存在于内存,如果存在就把这张图片删除,更新一下内存。假如我们新添加的图片非常大,超过了我们的内存大小,这个时候就有必要开始尝试移除图片的工作。移除图片的工作主要在trimToSize(maxSize)这个方法中进行,主要通过一个while循环,判断,如果内存没有超过设定的最大值,那么就不需要删除任何对象,直接break。如果超过了,我们就通过map集合的迭代器取出第一个数据项,这个数据项就是最近最少使用的那一项,然后把它移除,再更新一下内存,再次判断,知道内存小于设定的最大值为止。这样我们就实现了最近最少使用算法。

先进先出策略

再给大家将一个先进先出的策略吧,直接上源码:

public class FIFOLimitedMemoryCache extends LimitedMemoryCache {

  private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());

  public FIFOLimitedMemoryCache(int sizeLimit) {
      super(sizeLimit);
  }

  @Override
  public boolean put(String key, Bitmap value) {
      if (super.put(key, value)) {
        queue.add(value); //添加到队列的最后
        return true;
      } else {
        return false;
      }
  }

  @Override
  public Bitmap remove(String key) {
      Bitmap value = super.get(key);
      if (value != null) {
        queue.remove(value);
      }
      return super.remove(key);
  }

  @Override
  public void clear() {
      queue.clear();
      super.clear();
  }

  @Override
  protected int getSize(Bitmap value) {
      return value.getRowBytes() * value.getHeight();
  }

  @Override
  protected Bitmap removeNext() {
      return queue.remove(0);  //移除第一个
  }

  @Override
  protected Reference<Bitmap> createReference(Bitmap value) {
      return new WeakReference<Bitmap>(value);
  }
}
  • 可以看到,我们添加的时候是添加到queue的末尾,而移除的时候是移除第一个,这样就实现了FIFO(先进先出)算法。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值