Android上让人头疼的莫过于从网络上获取图片,然后显示图片,最后还要考虑到图片的回收问题,这之间只要有任何一个环节有问题都可能直接OOM。尤其在需要展示图片的列表页面,会加载大量网络上的图片,每当快速滑动列表的时候,都会很卡,甚至会因为内存溢出而崩溃。
这时就是ImageLoader的 show time
了。ImageLoader的目的是为了实现异步的网络图片加载、缓存及显示,支持多线程异步加载。
ImageLoader的工作原理是这样的:在显示图片的时候,它会先在内存中查找;如果没有,就去本地查找;如果还没有,就开一个新的线程去下载这张图片,下载成功会把图片同时缓存到内存和本地。
下载图片——将图片缓存在磁盘中——解码图片成为Bitmap——Bitmap的预处理——缓存在Bitmap内存中——Bitmap的后期处理——显示Bitmap
基于这个原理,我们可以在每一次退出一个页面的时候,把ImageLoader内存中的缓存全都清除,这样就节省了大量内存,反正下次再用到的时候从本地再取出来就是了。
此外,由于ImageLoader对图片是软引用的形式,所以内存中的图片会在内存不足的时候被系统回收(内存足够的时候不会对其进行垃圾回收)。
ImageLoader的三大组件:
ImageLoaderConfiguration——对图片缓存进行总体配置,包挎内存缓存的大小、本地缓存的大小和位置、日志、下载策略(FIFO还是LIFO)等等。
ImageLoader——我们一般使用displayImage来把URL对应的图片显示在ImageView上。
ImageLoaderOptions——在每个页面需要显示图片的地方,控制如何显示的细节,比如指定下载时的默认图(包括下载中、下载失败、URL为空等),是否将缓存放到内存或者本地磁盘。
特征:
1.多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
2.支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
3.支持图片的内存缓存,文件系统缓存或者SD卡缓存
4.支持图片下载过程的监听
5.根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
6.较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
7.提供在较慢的网络下对图片进行加载
具体使用
1.添加依赖:
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
2.添加权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
3.初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initImageLoader(); //初始化ImageLoader
}
private void initImageLoader() {
//创建默认的ImageLoader配置参数
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
ImageLoader.getInstance().init(configuration);
}
}
4.使用:
ImageLoader.getInstance().displayImage(url, mImageView);
ImageLoaderConfiguration
图片加载器ImageLoader的配置参数,使用了建造者模式,这里是直接使用了createDefault()方法创建一个默认的ImageLoaderConfiguration,当然我们还可以自己设置ImageLoaderConfiguration,设置如下:
private void initImageLoader() {
//创建默认的ImageLoader配置参数
//ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
//自己设置ImageLoaderConfiguration
String filePath = "";
// 如果外部储存卡处于挂载状态则使用外部储存卡,否则使用内部储存卡
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/gamehelper";
} else {
filePath = Environment.getDataDirectory().getAbsolutePath() + "/gamehelper";
}
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
// 设置内存缓存的大小(当超过这个值会去回收bitmap)
// Runtime.getRuntime().maxMemory()获取当前应用运行时内存的大小
.memoryCache(new LruMemoryCache((int) (Runtime.getRuntime().maxMemory() / 4)))
// 设置磁盘缓存的位置
.diskCache(new UnlimitedDiskCache(new File(filePath)))
.memoryCacheExtraOptions(200, 200)
// 设置加载图片的线程池数量
.threadPoolSize(8)
.build();
ImageLoader.getInstance().init(configuration);
}
DisplayImageOptions
他可以配置一些图片显示的选项,比如图片在加载中ImageView显示的图片,是否需要使用内存缓存,是否需要使用文件缓存等。
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.mipmap.ic_launcher)
.showImageOnFail(R.mipmap.ic_launcher)
.bitmapConfig(Bitmap.Config.RGB_565)
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
ImageLoader.getInstance().displayImage(url, mImageView, options);
配置一些常用的图片显示的选项:
package com.xiaoyehai.threadpool;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.display.CircleBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;
/**
* Created by se7en on 16/5/30.
*/
public class ImageLoaderUtil {
/**
* 获取一个默认图片的配置
*
* @return
*/
public static DisplayImageOptions getDefaultOption() {
DisplayImageOptions options = new DisplayImageOptions.Builder()
// 网络图片下载完成之前的预加载的默认图片
.showImageOnLoading(R.mipmap.ic_launcher)
// 网络图片下载失败后显示该默认图片
.showImageOnFail(R.mipmap.ic_launcher)
// 图片的质量
.bitmapConfig(Bitmap.Config.RGB_565)
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
return options;
}
/**
* 获取一个圆形图片的配置
*
* @return
*/
public static DisplayImageOptions getCircleOption(Integer strokeColor, float strokeWidth) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
// 网络图片下载完成之前的预加载的默认图片
.showImageOnLoading(R.mipmap.ic_launcher)
// 网络图片下载失败后显示该默认图片
.showImageOnFail(R.mipmap.ic_launcher)
// 图片的质量
.bitmapConfig(Bitmap.Config.RGB_565)
.cacheInMemory(true)
.cacheOnDisk(true)
.displayer(new CircleBitmapDisplayer(strokeColor, strokeWidth))
.build();
return options;
}
/**
* 获取一个圆角图片的配置
*
* @return
*/
public static DisplayImageOptions getRoundBitmapOption(int cornerRadiusPixels, int marginPixels) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
// 网络图片下载完成之前的预加载的默认图片
.showImageOnLoading(R.mipmap.ic_launcher)
// 网络图片下载失败后显示该默认图片
.showImageOnFail(R.mipmap.ic_launcher)
// 图片的质量
.bitmapConfig(Bitmap.Config.RGB_565)
.cacheInMemory(true)
.cacheOnDisk(true)
.displayer(new RoundedBitmapDisplayer(cornerRadiusPixels, marginPixels))
.build();
return options;
}
/**
* 获取一个渐显图片的配置
*
* @return
*/
public static DisplayImageOptions getFadeInOption(int durationMillis, boolean animateFromNetwork,
boolean animateFromDisk, boolean animateFromMemory) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
// 网络图片下载完成之前的预加载的默认图片
.showImageOnLoading(R.mipmap.ic_launcher)
// 网络图片下载失败后显示该默认图片
.showImageOnFail(R.mipmap.ic_launcher)
// 图片的质量
.bitmapConfig(Bitmap.Config.RGB_565)
.cacheInMemory(true)
.cacheOnDisk(true)
.displayer(new FadeInBitmapDisplayer(durationMillis, animateFromNetwork, animateFromDisk, animateFromMemory))
.build();
return options;
}
}
使用:
//获取一个圆形图片
ImageLoader.getInstance().displayImage(url,mImageView,ImageLoaderUtil.getCircleOption(Color.RED,2));
//获取一个圆角图片
ImageLoader.getInstance().displayImage(url,mImageView,ImageLoaderUtil.getRoundBitmapOption(50,20));
ImageLoader的优化
尽管ImageLoader很强大,但一直把图片缓存在内存中,会导致内存占用过高。虽然对图片的引用是软引用,软引用在内存不够的时候会被GC,但我们还是希望减少GC的次数,所以要经常手动清理ImageLoader中的缓存。
我们在上面MainActivity的 onDestroy()生命周期方法中,执行了ImageLoader的clearMemoryCache方法,以确保页面销毁时,把为了显示这个页面而增加的内存缓存清除。这样,即使到了下个页面要复用之前加载过的图片,虽然内存中没有了,根据ImageLoader的缓存策略,还是可以在本地磁盘上找到:
@Override
protected void onDestroy() {
// 回收该页面缓存在内存中的图片
imageLoader.clearMemoryCache();
super.onDestroy();
}
ImageLoaderConfiguration的配置参数:
File cacheDir = StorageUtils.getCacheDirectory(context); //缓存文件夹路径
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(); //开始构建
上面的这些就是所有的选项配置,我们在项目中不需要每一个都自己设置,一般使用createDefault()创建的ImageLoaderConfiguration就能使用,然后调用ImageLoader的init()方法将ImageLoaderConfiguration参数传递进去,ImageLoader使用单例模式。
DisplayImageOptions配置参数:
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();
获取图片下载进度
//获取图片下载进度
ImageLoader.getInstance().displayImage(url, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
}
});
如何同时加载大量图片:采用线程池优化高并发
如何提高加载速度:使用内存、磁盘缓存
如何避免OOM:加载的图片进行压缩,内存缓存具有相关的淘汰算法
如何避免ListView的图片错位:将要加载的图片和要显示的ImageView绑定, 在请求过程中判断该ImageView当前绑定的图片是否是当前请求的图片,不是则停止当前请求。
缺点:
此外,由于ImageLoader对图片是软引用的形式,所以内存中的图片会在内存不足的时候被系统回收(内存足够的时候不会对其进行垃圾回收)。
虽然这个框架有很好的缓存机制,有效的避免了OOM的产生,一般的情况下产生OOM的概率比较小,但是并不能保证OutOfMemoryError永远不发生,这个框架对于OutOfMemoryError做了简单的catch,保证我们的程序遇到OOM而不被crash掉,但是如果我们使用该框架经常发生OOM,我们应该怎么去改善呢?
1.减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5
2.在DisplayImageOptions选项中配置bitmapConfig为Bitmap.Config.RGB_565,因为默认是ARGB_8888, 使用RGB_565会比使用ARGB_8888少消耗2倍的内存
3.在ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(new WeakMemoryCache()) 或者不使用内存缓存
4.在DisplayImageOptions选项中设置.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(ImageScaleType.EXACTLY)