Universal-Image-Loader图片加载框架

来源:互联网 发布:linux 设置alias 编辑:程序博客网 时间:2024/05/17 08:59

引言

在前面的安卓面试系列–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
原创粉丝点击