您的位置:首页 > 移动开发 > Android开发

Android-Universal-Image-Loader 图片异步加载类库的使用

2015-05-13 10:22 459 查看
GITHUB上的下载路径为:https://github.com/nostra13/Android-Universal-Image-Loader

一、介绍

Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加载,缓存和显示。所以,如果你的程序里需要这个功能的话,那么不妨试试它。因为已经封装好了一些类和方法。我们 可以直接拿来用了。而不用重复去写了。其实,写一个这方面的程序还是比较麻烦的,要考虑多线程,缓存,内存溢出等很多方面。但是,你也可以参考这个例子来自己写出更好的程序。在此为大家介绍一下:





二、特点

1、多线程的图像加载,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等

2、支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置

3、支持图片的内存缓存,文件系统缓存或者SD卡缓存

4、支持图片下载过程的监听

5、根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存

6、较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片

7、提供在较慢的网络下对图片进行加载

简单描述一下这个项目的结构:每一个图片的加载和显示任务都运行在独立的线程中,除非这个图片缓存在内存中,这种情况下图片会立即显示。如果需要的图片缓存在本地,他们会开启一个独立的线程队列。如果在缓存中没有正确的图片,任务线程会从线程池中获取,因此,快速显示缓存图片时不会有明显的障碍。(别人那边借鉴的这段)

流程图:



三、使用方法

这是 一个开源的Android关于下载显示图片的工具类,在这个下载包里面jar文件,用于我们导入项目使用,具体使用方法在包里面也含有。下面是一个例子:

[java] view
plaincopy





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

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

由于是使用过程中会图片获取要通过网络,并且有缓存设置,所以这2个权限必须要有。

很多人想知道如何设置缓存的目录,可以通过以下方法:

[java] view
plaincopy





File cacheDir = StorageUtils.getOwnCacheDirectory(getApplicationContext(), "imageloader/Ca
che");

//这个是你希望的缓存文件的目录:imageloader/Cache

下面我们就来开始看如何使用这个图片异步加载库吧:

一.

先要配置ImageLoaderConfiguration这个类实现全局ImageLoader的实现情况。

可以选择在Application中初始化设置该类。

简单配置:

//创建默认的ImageLoader配置参数

ImageLoaderConfiguration configuration = ImageLoaderConfiguration

.createDefault(this);

//Initialize ImageLoader with configuration.

ImageLoader.getInstance().init(configuration);

自定义配置:

[java] view
plaincopy





ImageLoaderConfiguration config = new ImageLoaderConfiguration

.Builder(context)

.memoryCacheExtraOptions(480, 800) // max width, max height,即保存的每个缓存文件的最大长宽

.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) // Can slow ImageLoader, use it carefully (Better don't use it)/设置缓存的详细信息,最好不要设置这个

.threadPoolSize(3)//线程池内加载的数量

.threadPriority(Thread.NORM_PRIORITY - 2)

.denyCacheImageMultipleSizesInMemory()

.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // You can pass your own memory cache implementation/你可以通过自己的内存缓存实现

.memoryCacheSize(2 * 1024 * 1024)

.discCacheSize(50 * 1024 * 1024)

.discCacheFileNameGenerator(new Md5FileNameGenerator())//将保存的时候的URI名称用MD5 加密

.tasksProcessingOrder(QueueProcessingType.LIFO)

.discCacheFileCount(100) //缓存的文件数量

.discCache(new UnlimitedDiscCache(cacheDir))//自定义缓存路径

.defaultDisplayImageOptions(DisplayImageOptions.createSimple())

.imageDownloader(new BaseImageDownloader(context, 5 * 1000, 30 * 1000)) // connectTimeout (5 s), readTimeout (30 s)超时时间

.writeDebugLogs() // Remove for release app

.build();//开始构建

// Initialize ImageLoader with configuration.

以上的配置看个人需求进行选择,不是所有都要进行配置。

配置好ImageLoaderConfiguration后,调用以下方法来实现初始化:

[java] view
plaincopy





ImageLoader.getInstance().init(config);//全局初始化此配置

注:ImageLoaderConfiguration 配置中的.discCacheFileNameGenerator()方法是将缓存下来的文件以什么方式命名

里面可以调用的方法有 1.new Md5FileNameGenerator() //使用MD5对UIL进行加密命名

2.new HashCodeFileNameGenerator()//使用HASHCODE对UIL进行加密命名

二.

使用ImageLoader进行图片加载的时候,先要实例化ImageLoader,调用以下方法进行实例化,在每个布局里面都要实例化后再使用。

[java] view
plaincopy





protected ImageLoader imageLoader = ImageLoader.getInstance();

之后进行显示的图片的各种格式DisplayImageOptions 的设置:

[java] view
plaincopy





DisplayImageOptions options;

options = new DisplayImageOptions.Builder()

.showImageOnLoading(R.drawable.ic_launcher) //设置图片在下载期间显示的图片

.showImageForEmptyUri(R.drawable.ic_launcher)//设置图片Uri为空或是错误的时候显示的图片

.showImageOnFail(R.drawable.ic_launcher) //设置图片加载/解码过程中错误时候显示的图片

.cacheInMemory(true)//设置下载的图片是否缓存在内存中

.cacheOnDisc(true)//设置下载的图片是否缓存在SD卡中

.considerExifParams(true)
//是否考虑JPEG图像EXIF参数(旋转,翻转)

.imageScaleType(ImageScaleType.EXACTLY_STRETCHED)//设置图片以如何的编码方式显示

.bitmapConfig(Bitmap.Config.RGB_565)//设置图片的解码类型//

.decodingOptions(android.graphics.BitmapFactory.Options decodingOptions)//设置图片的解码配置

//.delayBeforeLoading(int delayInMillis)//int delayInMillis为你设置的下载前的延迟时间

//设置图片加入缓存前,对bitmap进行设置

//.preProcessor(BitmapProcessor preProcessor)

.resetViewBeforeLoading(true)//设置图片在下载前是否重置,复位

.displayer(new RoundedBitmapDisplayer(20))//是否设置为圆角,弧度为多少

.displayer(new FadeInBitmapDisplayer(100))//是否图片加载好后渐入的动画时间

.build();//构建完成

按照你所需要的配置去设置,如果不需要的就可以不做配置。

注:

以上配置中的:

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()正常显示一张图片  

之后按照需求调用

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

所以loadImageSync()方法我们就不去使用

loadimage()加载图片

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

[java] view
plaincopy





final ImageView mImageView = (ImageView) findViewById(R.id.image);

String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";

ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {

@Override

public void onLoadingStarted(String imageUri, View view) {

}

@Override

public void onLoadingFailed(String imageUri, View view,

FailReason failReason) {

}

@Override

public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {

mImageView.setImageBitmap(loadedImage);

}

@Override

public void onLoadingCancelled(String imageUri, View view) {

}

});

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

[java] view
plaincopy





final ImageView mImageView = (ImageView) findViewById(R.id.image);

String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";

ImageLoader.getInstance().loadImage(imageUrl, new SimpleImageLoadingListener(){

@Override

public void onLoadingComplete(String imageUri, View view,

Bitmap loadedImage) {

super.onLoadingComplete(imageUri, view, loadedImage);

mImageView.setImageBitmap(loadedImage);

}

});

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

[java] view
plaincopy





final ImageView mImageView = (ImageView) findViewById(R.id.image);

String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";

ImageSize mImageSize = new ImageSize(100, 100);

ImageLoader.getInstance().loadImage(imageUrl, mImageSize, new SimpleImageLoadingListener(){

@Override

public void onLoadingComplete(String imageUri, View view,

Bitmap loadedImage) {

super.onLoadingComplete(imageUri, view, loadedImage);

mImageView.setImageBitmap(loadedImage);

}

});

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

[java] view
plaincopy





DisplayImageOptions options = new DisplayImageOptions.Builder()

.showImageOnLoading(R.drawable.ic_stub) // resource or drawable

.showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable

.showImageOnFail(R.drawable.ic_error) // resource or drawable

.resetViewBeforeLoading(false) // default

.delayBeforeLoading(1000)

.cacheInMemory(false) // default

.cacheOnDisk(false) // default

.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

.handler(new Handler()) // default

.build();

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

[java] view
plaincopy





final ImageView mImageView = (ImageView) findViewById(R.id.image);

String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";

ImageSize mImageSize = new ImageSize(100, 100);

//显示图片的配置

DisplayImageOptions options = new DisplayImageOptions.Builder()

.cacheInMemory(true)

.cacheOnDisk(true)

.bitmapConfig(Bitmap.Config.RGB_565)

.build();

ImageLoader.getInstance().loadImage(imageUrl, mImageSize, options, new SimpleImageLoadingListener(){

@Override

public void onLoadingComplete(String imageUri, View view,

Bitmap loadedImage) {

super.onLoadingComplete(imageUri, view, loadedImage);

mImageView.setImageBitmap(loadedImage);

}

});

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

displayImage()加载图片

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

[java] view
plaincopy





final 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);

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





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

[java] view
plaincopy





public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

//创建默认的ImageLoader配置参数

ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)

.writeDebugLogs() //打印log信息

.build();

//Initialize ImageLoader with configuration.

ImageLoader.getInstance().init(configuration);

}

}

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



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

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

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

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

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

[java] view
plaincopy





imageLoader.displayImage(imageUrl, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {

@Override

public void onProgressUpdate(String imageUri, View view, int current,

int total) {

}

});

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

加载其他来源的图片

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

[java] view
plaincopy





//显示图片的配置

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();

final ImageView mImageView = (ImageView) findViewById(R.id.image);

String imagePath = "/mnt/sdcard/image.png";

String imageUrl = Scheme.FILE.wrap(imagePath);

// String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";

imageLoader.displayImage(imageUrl, mImageView, options);

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

[java] view
plaincopy





//图片来源于Content provider

String contentprividerUrl = "content://media/external/audio/albumart/13";

//图片来源于assets

String assetsUrl = Scheme.ASSETS.wrap("image.png");

//图片来源于

String drawableUrl = Scheme.DRAWABLE.wrap("R.drawable.image");

String imageUri = "http://site.com/image.png"; // from Web

String imageUri = "file:///mnt/sdcard/image.png"; // from SD card

String imageUri = "content://media/external/audio/albumart/13"; // from content provider

String imageUri = "assets://image.png"; // from assets

String imageUri = "drawable://" + R.drawable.image; // from drawables (only images, non-9patch)

GirdView,ListView加载图片

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

[java] view
plaincopy





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

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

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

清理指定缓存

[java] view
plaincopy





MemoryCacheUtils.removeFromCache(imageUri, ImageLoader.getInstance().getMemoryCache());

DiskCacheUtils.removeFromCache(imageUri, ImageLoader.getInstance().getDiskCache());

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)
ImageLoader是根据ImageView的height,width确定图片的宽高。

ImageLoaderConfiguration必须配置并且全局化的初始化这个配置ImageLoader.getInstance().init(config);
否则也会出现错误提示

上述提到的2个权限必须加入,否则会出错

通过上面这些,相信大家对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设定的值来裁剪图片

1.纯粹为了加载默认配置的一个图片的

方法:

public void displayImage(String uri, ImageView imageView) {}

具体实现:

[java] view
plaincopy





ImageLoader.getInstance().displayImage(imageUrl, imageView); // imageUrl代表图片的URL地址,imageView代表承载图片的IMAGEVIEW控件

2.加载自定义配置的一个图片的

方法:

public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {}

具体实现:

[java] view
plaincopy





ImageLoader.getInstance().displayImage(imageUrl, imageView,options); // imageUrl代表图片的URL地址,imageView代表承载图片的IMAGEVIEW控件 , options代表DisplayImageOptions配置文件

3.图片加载时候带加载情况的监听

方法:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,ImageLoadingListener listener) {}

ImageLoadingListener 用于监听图片的下载情况。

具体实现:

[java] view
plaincopy





imageLoader.displayImage(imageUrl, imageView, options, new ImageLoadingListener() {

@Override

public void onLoadingStarted() {

//开始加载的时候执行

}

@Override

public void onLoadingFailed(FailReason failReason) {

//加载失败的时候执行

}

@Override

public void onLoadingComplete(Bitmap loadedImage) {

//加载成功的时候执行

}

@Override

public void onLoadingCancelled() {

//加载取消的时候执行

}});

4.图片加载时候,带监听又带加载进度条的情况

调用:

public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,

ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {}

具体实现:

[java] view
plaincopy





imageLoader.displayImage(imageUrl, imageView, options, new ImageLoadingListener() {

@Override

public void onLoadingStarted() {

//开始加载的时候执行

}

@Override

public void onLoadingFailed(FailReason failReason) {

//加载失败的时候执行

}

@Override

public void onLoadingComplete(Bitmap loadedImage) {

//加载成功的时候执行

}

@Override

public void onLoadingCancelled() {

//加载取消的时候执行

},new ImageLoadingProgressListener() {

@Override

public void onProgressUpdate(String imageUri, View view, int current,int total) {

//在这里更新 ProgressBar的进度信息

}

});

图片缓存策略

内存缓存

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

强引用是指创建一个对象并把这个对象赋给一个引用变量, 强引用有引用变量指向时永远不会被垃圾回收。即使内存不足的时候宁愿报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
plaincopy





ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)

.memoryCache(new WeakMemoryCache())

.build();

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

[java] view
plaincopy





package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;

import java.util.Collection;

import java.util.HashSet;

import java.util.LinkedHashMap;

import java.util.Map;

/**

* A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to

* 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

* become eligible for garbage collection.<br />

* <br />

* <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.

*

* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)

* @since 1.8.1

*/

public class LruMemoryCache implements MemoryCacheAware<String, Bitmap> {

private final LinkedHashMap<String, Bitmap> map;

private final int maxSize;

/** Size of this cache in bytes */

private int size;

/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */

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);

}

/**

* Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head

* of the queue. This returns null if a Bitmap is not cached.

*/

@Override

public final Bitmap get(String key) {

if (key == null) {

throw new NullPointerException("key == null");

}

synchronized (this) {

return map.get(key);

}

}

/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */

@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;

}

/**

* Remove the eldest entries until the total of remaining entries is at or below the requested size.

*

* @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.

*/

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);

}

}

}

/** Removes the entry for {@code key} if it exists. */

@Override

public final void 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);

}

}

}

@Override

public Collection<String> keys() {

synchronized (this) {

return new HashSet<String>(map.keySet());

}

}

@Override

public void clear() {

trimToSize(-1); // -1 will evict 0-sized elements

}

/**

* Returns the size {@code Bitmap} in bytes.

* <p/>

* An entry's size must not change while it is in the cache.

*/

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);

}

}

我们可以看到这个类中维护的是一个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的第一个元素,所以就实现了先进先出的缓存策略,其他的缓存都类似,有兴趣的可以去看看。

硬盘缓存

接下来就给大家分析分析硬盘缓存的策略,这个框架也提供了几种常见的缓存策略,当然如果你觉得都不符合你的要求,你也可以自己去扩展

FileCountLimitedDiscCache(可以设定缓存图片的个数,当超过设定值,删除掉最先加入到硬盘的文件)
LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)
TotalSizeLimitedDiscCache(设定缓存bitmap的最大值,当超过这个值,删除最先加入到硬盘的文件)
UnlimitedDiscCache(这个缓存类没有任何的限制)

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

[java] view
plaincopy





/*******************************************************************************

* Copyright 2011-2013 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 com.nostra13.universalimageloader.cache.disc.LimitedDiscCache;

import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;

import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;

import com.nostra13.universalimageloader.utils.L;

import java.io.File;

/**

* Disc cache limited by total cache size. If cache size exceeds specified limit then file with the most oldest last

* usage date will be deleted.

*

* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)

* @see LimitedDiscCache

* @since 1.0.0

*/

public class TotalSizeLimitedDiscCache extends LimitedDiscCache {

private static final int MIN_NORMAL_CACHE_SIZE_IN_MB = 2;

private static final int MIN_NORMAL_CACHE_SIZE = MIN_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024;

/**

* @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's

* needed for right cache limit work.

* @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the

* most oldest last usage date will be deleted.

*/

public TotalSizeLimitedDiscCache(File cacheDir, int maxCacheSize) {

this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxCacheSize);

}

/**

* @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's

* needed for right cache limit work.

* @param fileNameGenerator Name generator for cached files

* @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the

* most oldest last usage date will be deleted.

*/

public TotalSizeLimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int maxCacheSize) {

super(cacheDir, fileNameGenerator, maxCacheSize);

if (maxCacheSize < MIN_NORMAL_CACHE_SIZE) {

L.w("You set too small disc cache size (less than %1$d Mb)", MIN_NORMAL_CACHE_SIZE_IN_MB);

}

}

@Override

protected int getSize(File file) {

return (int) file.length();

}

}

这个类是继承LimitedDiscCache,除了两个构造函数之外,还重写了getSize()方法,返回文件的大小,接下来我们就来看看LimitedDiscCache

[java] view
plaincopy





/*******************************************************************************

* Copyright 2011-2013 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;

import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;

import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;

import java.io.File;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

import java.util.Map.Entry;

import java.util.Set;

import java.util.concurrent.atomic.AtomicInteger;

/**

* Abstract disc cache limited by some parameter. If cache exceeds specified limit then file with the most oldest last

* usage date will be deleted.

*

* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)

* @see BaseDiscCache

* @see FileNameGenerator

* @since 1.0.0

*/

public abstract class LimitedDiscCache extends BaseDiscCache {

private static final int INVALID_SIZE = -1;

//记录缓存文件的大小

private final AtomicInteger cacheSize;

//缓存文件的最大值

private final int sizeLimit;

private final Map<File, Long> lastUsageDates = Collections.synchronizedMap(new HashMap<File, Long>());

/**

* @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's

* needed for right cache limit work.

* @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date

* will be deleted.

*/

public LimitedDiscCache(File cacheDir, int sizeLimit) {

this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), sizeLimit);

}

/**

* @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's

* needed for right cache limit work.

* @param fileNameGenerator Name generator for cached files

* @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date

* will be deleted.

*/

public LimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int sizeLimit) {

super(cacheDir, fileNameGenerator);

this.sizeLimit = sizeLimit;

cacheSize = new AtomicInteger();

calculateCacheSizeAndFillUsageMap();

}

/**

* 另开线程计算cacheDir里面文件的大小,并将文件和最后修改的毫秒数加入到Map中

*/

private void calculateCacheSizeAndFillUsageMap() {

new Thread(new Runnable() {

@Override

public void run() {

int size = 0;

File[] cachedFiles = cacheDir.listFiles();

if (cachedFiles != null) { // rarely but it can happen, don't know why

for (File cachedFile : cachedFiles) {

//getSize()是一个抽象方法,子类自行实现getSize()的逻辑

size += getSize(cachedFile);

//将文件的最后修改时间加入到map中

lastUsageDates.put(cachedFile, cachedFile.lastModified());

}

cacheSize.set(size);

}

}

}).start();

}

/**

* 将文件添加到Map中,并计算缓存文件的大小是否超过了我们设置的最大缓存数

* 超过了就删除最先加入的那个文件

*/

@Override

public void put(String key, File file) {

//要加入文件的大小

int valueSize = getSize(file);

//获取当前缓存文件大小总数

int curCacheSize = cacheSize.get();

//判断是否超过设定的最大缓存值

while (curCacheSize + valueSize > sizeLimit) {

int freedSize = removeNext();

if (freedSize == INVALID_SIZE) break; // cache is empty (have nothing to delete)

curCacheSize = cacheSize.addAndGet(-freedSize);

}

cacheSize.addAndGet(valueSize);

Long currentTime = System.currentTimeMillis();

file.setLastModified(currentTime);

lastUsageDates.put(file, currentTime);

}

/**

* 根据key生成文件

*/

@Override

public File get(String key) {

File file = super.get(key);

Long currentTime = System.currentTimeMillis();

file.setLastModified(currentTime);

lastUsageDates.put(file, currentTime);

return file;

}

/**

* 硬盘缓存的清理

*/

@Override

public void clear() {

lastUsageDates.clear();

cacheSize.set(0);

super.clear();

}

/**

* 获取最早加入的缓存文件,并将其删除

*/

private int removeNext() {

if (lastUsageDates.isEmpty()) {

return INVALID_SIZE;

}

Long oldestUsage = null;

File mostLongUsedFile = null;

Set<Entry<File, Long>> entries = lastUsageDates.entrySet();

synchronized (lastUsageDates) {

for (Entry<File, Long> entry : entries) {

if (mostLongUsedFile == null) {

mostLongUsedFile = entry.getKey();

oldestUsage = entry.getValue();

} else {

Long lastValueUsage = entry.getValue();

if (lastValueUsage < oldestUsage) {

oldestUsage = lastValueUsage;

mostLongUsedFile = entry.getKey();

}

}

}

}

int fileSize = 0;

if (mostLongUsedFile != null) {

if (mostLongUsedFile.exists()) {

fileSize = getSize(mostLongUsedFile);

if (mostLongUsedFile.delete()) {

lastUsageDates.remove(mostLongUsedFile);

}

} else {

lastUsageDates.remove(mostLongUsedFile);

}

}

return fileSize;

}

/**

* 抽象方法,获取文件大小

* @param file

* @return

*/

protected abstract int getSize(File file);

}

在构造方法中,第69行有一个方法calculateCacheSizeAndFillUsageMap(),该方法是计算cacheDir的文件大小,并将文件和文件的最后修改时间加入到Map中

然后是将文件加入硬盘缓存的方法put(),在106行判断当前文件的缓存总数加上即将要加入缓存的文件大小是否超过缓存设定值,如果超过了执行removeNext()方法,接下来就来看看这个方法的具体实现,150-167中找出最先加入硬盘的文件,169-180中将其从文件硬盘中删除,并返回该文件的大小,删除成功之后成员变量cacheSize需要减掉改文件大小。

FileCountLimitedDiscCache这个类实现逻辑跟TotalSizeLimitedDiscCache是一样的,区别在于getSize()方法,前者返回1,表示为文件数是1,后者返回文件的大小。

等我写完了这篇文章,我才发现FileCountLimitedDiscCache和TotalSizeLimitedDiscCache在最新的源码中已经删除了,加入了LruDiscCache,由于我的是之前的源码,所以我也不改了,大家如果想要了解LruDiscCache可以去看最新的源码,我这里就不介绍了,还好内存缓存的没变化,下面分析的是最新的源码中的部分,我们在使用中可以不自行配置硬盘缓存策略,直接用DefaultConfigurationFactory中的就行了

我们看DefaultConfigurationFactory这个类的createDiskCache()方法

[java] view
plaincopy





/**

* Creates default implementation of {@link DiskCache} depends on incoming parameters

*/

public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,

long diskCacheSize, int diskCacheFileCount) {

File reserveCacheDir = createReserveDiskCacheDir(context);

if (diskCacheSize > 0 || diskCacheFileCount > 0) {

File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);

LruDiscCache diskCache = new LruDiscCache(individualCacheDir, diskCacheFileNameGenerator, diskCacheSize,

diskCacheFileCount);

diskCache.setReserveCacheDir(reserveCacheDir);

return diskCache;

} else {

File cacheDir = StorageUtils.getCacheDirectory(context);

return new UnlimitedDiscCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);

}

}

如果我们在ImageLoaderConfiguration中配置了diskCacheSize和diskCacheFileCount,他就使用的是LruDiscCache,否则使用的是UnlimitedDiscCache,在最新的源码中还有一个硬盘缓存类可以配置,那就是LimitedAgeDiscCache,可以在ImageLoaderConfiguration.diskCache(...)配置

参考:/article/1970421.html

/article/1645774.html

/article/1645773.html

/article/1645772.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐