一、概述
现在开发界的图片加载使用框架真是越来越丰富了,例如ImageLoader、Glide、Picasso、Fresco等,选择非常多,而且都还不错。当然对于那些患有严重的“选择恐惧症”的人来说,到底用那个是一个相当头疼的问题。为了减轻大家的选择痛苦,我今天就选这里面的其中一个ImageLoader讲,如果大家觉得我封装的这个很好用,那就用它吧。若不好的话,那我下次换一个讲。
或许会有人问我,“这四个你为什么会选择讲ImageLoader而不是其它的呢?我来谈谈我选ImageLoader的理由
1、支持多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
2、高度的可定制性,支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
3、强大的图片缓存机制,支持图片的内存缓存,文件系统缓存或者SD卡缓存
4、支持图片下载过程的监听
5、根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
8、我只用过ImageLoader和Picasso,其它的没有用过(这个理由说的我好尴尬、心虚)
二、开发实战
前面说了点废话,接下来进入正题。我们接下来的开发实战分两步走,首先是搭建ImageLoader使用环境,然后就是封装ImageLoader。
1、搭建ImageLoader使用环境
这一步很好实现,就是在build.gradle文件中添加一句话。注意,这里大家最好下载最新的。
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
还有就是要在manifest文件中添加两个权限,分别是网络访问权限和写文件权限,毕竟人家是可以加载网络图片且需要缓存的。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2、封装ImageLoader
先看一下我画的封装思路图
对着这个思路图咱们一步一步的实现,首先是默认参数配置及提供必要的参数配置。
对于自己的封装的ImageLoader类,我们可以把它设置成为单例的,例如:
public static ImageLoaderUtil getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoaderUtil.class) {
if (mInstance == null) {
mInstance = new ImageLoaderUtil(context);
}
}
}
return mInstance;
}
接下来配置ImageLoader基本的默认参数,有了这些参数配置,下载、加载大图片就基本可以避免OOM的问题了
/**
* 默认的参数值
*/
private static final int THREAD_COUNT = 4; // 标明UIL最多可以有多少条线程
private static final int PROPRITY = 2; // 标明图片加载的一个优先级
private static final int DISK_CACHE_SIZE = 50 * 1024 * 1024; // 标明UIL可以最多缓存多少图片(50M)
private static final int CONNECTION_TIME_OUT = 5 * 1000; // 连接的超时时间(5秒)
private static final int READ_TIME_OUT = 30 * 1000; // 读取的超时时间(30秒)
上面只是设置,好么有配置。接下来看看配置,这里的配置分两个部分,一个是ImageLoaderConfiguration,另一个是DisplayImageOptions.
首先来配置ImageLoaderConfiguration,以下的配置是最基本的配置项,也是必须要的有的!注释也都很详尽,我就不罗嗦了。
/**
* 单例模式的私有构造方法
*
* @param context
*/
private ImageLoaderUtil(Context context) {
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration
.Builder(context)
.threadPoolSize(THREAD_COUNT) // 配置图片下载线程的最大数量
.threadPriority(Thread.NORM_PRIORITY - PROPRITY) // 设置优先级
.denyCacheImageMultipleSizesInMemory() // 防止缓存多套尺寸的图片到我们的内存中
.memoryCache(new WeakMemoryCache()) // 使用弱引用内存缓存
.diskCacheSize(DISK_CACHE_SIZE) // 分配硬盘缓存大小
.diskCacheFileNameGenerator(new Md5FileNameGenerator()) // 使用MD5命名文件
.tasksProcessingOrder(QueueProcessingType.LIFO) // 图片下载顺序
.defaultDisplayImageOptions(getDefaultOptions()) // 默认图片加载的Options
.imageDownloader(new BaseImageDownloader(context, CONNECTION_TIME_OUT,
READ_TIME_OUT)) // 设置图片下载器
.writeDebugLogs() // debug环境下会输出日志
.build();
// 初始化配置,创建ImageLoader实例
ImageLoader.getInstance().init(configuration);
mImageLoader = ImageLoader.getInstance();
}
接下来看看DisplayImageOptions的配置。
/**
* 实现我们的Options
*
* @return
*/
private DisplayImageOptions getDefaultOptions() {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageForEmptyUri(R.drawable.xadsdk_img_error) // 在我们图片地址为空的时候加载的默认图
.showImageOnFail(R.drawable.xadsdk_img_error) // 图片下载失败的时候要显示的图片
.cacheInMemory(true) // 设置图片可以缓存到内存中
.cacheOnDisk(true) // 设置图片可以缓存到硬盘中
.bitmapConfig(Bitmap.Config.RGB_565) // 使用的图片解码类型
.decodingOptions(new BitmapFactory.Options()) // 图片的解码配置
.build();
return options;
}
基本配置都搞定后,看看思路图的第二步,对外提供接口。在前面我在提IamgeLoader的特性时提到了支持图片下载过程的监听,这是个什么监听?给大家看看源码就明白了
public interface ImageLoadingListener {
/**
* Is called when image loading task was started
*
* @param imageUri Loading image URI
* @param view View for image
*/
void onLoadingStarted(String imageUri, View view);
/**
* Is called when an error was occurred during image loading
*
* @param imageUri Loading image URI
* @param view View for image. Can be null.
* @param failReason {@linkplain com.nostra13.universalimageloader.core.assist.FailReason The reason} why image
* loading was failed
*/
void onLoadingFailed(String imageUri, View view, FailReason failReason);
/**
* Is called when image is loaded successfully (and displayed in View if one was specified)
*
* @param imageUri Loaded image URI
* @param view View for image. Can be null.
* @param loadedImage Bitmap of loaded and decoded image
*/
void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
/**
* Is called when image loading task was cancelled because View for image was reused in newer task
*
* @param imageUri Loading image URI
* @param view View for image. Can be null.
*/
void onLoadingCancelled(String imageUri, View view);
这个接口里声明了四个方法,分别是onLoadingStarted()下载开始,onLoadingFailed()下载失败,onLoadingComplete()下载完成,onLoadingCancelled()下载取消。因此我们只要实现了这四个方法,就可以做我们想要的操作,这里我大家看一下我的实现:
public abstract class IamgeLoaderUtilsListener implements ImageLoadingListener {
@Override
public void onLoadingCancelled(String imageUri, View view) {
}
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
}
@Override
public abstract void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
}
这里我定义了一个抽象类,并实现了ImageLoadingListener接口,我在项目中只想监听图片是否完成,所以就将
onLoadingComplete()设置成为抽象方法。在创建这个类时必须实现这个抽象方法,当然这个地方大家因各自的需求不同,可以自己设置。
最后就是对外提供加载图片的方法了,源码中提供了displayImage()方法,这里为了适应各种显示图片的需求,我稍微封装了一下。
/**
* 加载图片API,然后重载几个方法
* @param imageView
* @param url
* @param options
* @param listener
*/
public void displayImage(ImageView imageView, String url, DisplayImageOptions options,
IamgeLoaderUtilsListener listener) {
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,options,listener);
}
}
public void displayImage(ImageView imageView, String url,IamgeLoaderUtilsListener listener){
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,null,listener);
}
}
public void displayImage(String url,IamgeLoaderUtilsListener listener){
if(mImageLoader != null) {
mImageLoader.loadImage(url, null, null, listener);
}
}
public void displayImage(ImageView imageView, String url){
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,null,null);
}
}
好了,现在我将完整的代码贴出来,让大家了解全貌
public class ImageLoaderUtil {
/**
* 默认的参数值
*/
private static final int THREAD_COUNT = 4; // 标明UIL最多可以有多少条线程
private static final int PROPRITY = 2; // 标明图片加载的一个优先级
private static final int DISK_CACHE_SIZE = 50 * 1024 * 1024; // 标明UIL可以最多缓存多少图片(50M)
private static final int CONNECTION_TIME_OUT = 5 * 1000; // 连接的超时时间(5秒)
private static final int READ_TIME_OUT = 30 * 1000; // 读取的超时时间(30秒)
private static ImageLoader mImageLoader = null;
private static ImageLoaderUtil mInstance = null;
public static ImageLoaderUtil getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoaderUtil.class) {
if (mInstance == null) {
mInstance = new ImageLoaderUtil(context);
}
}
}
return mInstance;
}
/**
* 单例模式的私有构造方法
*
* @param context
*/
private ImageLoaderUtil(Context context) {
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration
.Builder(context)
.threadPoolSize(THREAD_COUNT) // 配置图片下载线程的最大数量
.threadPriority(Thread.NORM_PRIORITY - PROPRITY) // 设置优先级
.denyCacheImageMultipleSizesInMemory() // 防止缓存多套尺寸的图片到我们的内存中
.memoryCache(new WeakMemoryCache()) // 使用弱引用内存缓存
.diskCacheSize(DISK_CACHE_SIZE) // 分配硬盘缓存大小
.diskCacheFileNameGenerator(new Md5FileNameGenerator()) // 使用MD5命名文件
.tasksProcessingOrder(QueueProcessingType.LIFO) // 图片下载顺序
.defaultDisplayImageOptions(getDefaultOptions()) // 默认图片加载的Options
.imageDownloader(new BaseImageDownloader(context, CONNECTION_TIME_OUT,
READ_TIME_OUT)) // 设置图片下载器
.writeDebugLogs() // debug环境下会输出日志
.build();
// 初始化配置,创建ImageLoader实例
ImageLoader.getInstance().init(configuration);
mImageLoader = ImageLoader.getInstance();
}
/**
* 实现我们的Options
*
* @return
*/
private DisplayImageOptions getDefaultOptions() {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageForEmptyUri(R.drawable.xadsdk_img_error) // 在我们图片地址为空的时候加载的默认图
.showImageOnFail(R.drawable.xadsdk_img_error) // 图片下载失败的时候要显示的图片
.cacheInMemory(true) // 设置图片可以缓存到内存中
.cacheOnDisk(true) // 设置图片可以缓存到硬盘中
.bitmapConfig(Bitmap.Config.RGB_565) // 使用的图片解码类型
.decodingOptions(new BitmapFactory.Options()) // 图片的解码配置
.build();
return options;
}
/**
* 加载图片API,然后重载几个方法
* @param imageView
* @param url
* @param options
* @param listener
*/
public void displayImage(ImageView imageView, String url, DisplayImageOptions options,
IamgeLoaderUtilsListener listener) {
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,options,listener);
}
}
public void displayImage(ImageView imageView, String url,IamgeLoaderUtilsListener listener){
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,null,listener);
}
}
public void displayImage(String url,IamgeLoaderUtilsListener listener){
if(mImageLoader != null) {
mImageLoader.loadImage(url, null, null, listener);
}
}
public void displayImage(ImageView imageView, String url){
if (mImageLoader != null) {
mImageLoader.displayImage(url,imageView,null,null);
}
}
}
接下来最后一步,如何使用。非常方便,一句搞定
ImageLoaderUtil.getInstance(mContext).displayImage(imageView, imageUrl);
如果大家想要监听图片下载是否完成,可以这样
ImageLoaderUtil.getInstance(getApplicationContext())
.displayImage(imageView,imageUrl, new IamgeLoaderUtilsListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
});
好了,IamgeLoader封装就此结束。如果大家觉的我上面写的有不对的地方,欢迎大家指正,非常感谢。