简述
一个加载图片的框架,后续的代码基于1.9.5版本进行分析。最后一篇会进行总结,现在先通过基础流程和一些重要的类来看整个的运行逻辑以及框架的设计
ImageLoader
框架的入口
//全局配置,有且只有一个,多次配置只以第一个为准
private ImageLoaderConfiguration configuration;
//这个其实是线程管理类,顾名思义就是引擎吧
private ImageLoaderEngine engine;
//读取流程监听,默认是空实现
private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();
//单例实现类,DCL结合volatile才可以真正实现完整单例
private volatile static ImageLoader instance;
/**
* DCL单例实现
* */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
protected ImageLoader() {
}
/**
* 初始化ImageLoader的全局配置,同时也是ImageLoaderEngine的配置
* 如果说在已经设置了一个ImageLoaderConfiguration的情况下再次设置
* 该方法并不会生效
* 如果想要重新设置配置,那么首先需要destory()整个ImageLoader
* 然后再重新设置
*/
public synchronized void init(ImageLoaderConfiguration configuration) {
if (configuration == null) {
throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
}
//注意这里配置只能设置一次,多次init也只能以第一次为准
//主要是因为这个配置会新建一个运行引擎,所以说在旧的引擎没有停止之前
//新设置的配置都不会生效
if (this.configuration == null) {
L.d(LOG_INIT_CONFIG);
engine = new ImageLoaderEngine(configuration);
this.configuration = configuration;
} else {
L.w(WARNING_RE_INIT_CONFIG);
}
}
ImageLoader默认是一个单例实现,也就是说还是通过对象的形式来进行使用,然后提供不同的参数的重载方法,对于一般的项目来说为了统一图片加载的方式,也会使用这种方式进行封装(这并不是唯一的选择,比方说还有Builder)。
接下来看几个比较典型的图片加载方法
/**
* 添加一个加载图片,并且展示图片的任务到执行池中。
* 后续图片会自动展示到指定的载体上面,如果有必要的话
* ImageLoader如果需要运作的话,必须要保证调用init(ImageLoaderConfiguration)初始化了ImageLoaderEngine
* 否则并没有可以使用的执行池
*
* @param uri 图片的URI,网络图片"http://site.com/image.png" ,手机硬盘上面的图片"file:///mnt/sdcard/image.png" 等等
* @param imageAware 图片的载体,一般用于展示图片
* @param options 图片解析和展示所使用的配置。可以为null,会使用默认的DisplayImageOptions
* @param targetSize 图片的尺寸,可以为null,这样图片的尺寸会取决与view的测量情况
* @param listener 加载图片状态变化的监听,如果该方法调用在UI线程,那么监听的回调也会在UI线程,
* 请求uri为空的时候依然会工作
* @param progressListener 加载图片完成度的监听,如果该方法调用在UI线程,那么监听的回调也会在UI线程,
* 如果说该图片从硬盘缓存中获取,那么此监听也会工作
*/
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
//ImageLoader在使用之前必须初始化ImageLoaderConfiguration
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {//默认的listener为空实现
listener = defaultListener;
}
if (options == null) {//默认的解析和加载配置,不会使用缓存机制
options = configuration.defaultDisplayImageOptions;
}
if (TextUtils.isEmpty(uri)) {//请求的链接为null或者""...这里还是需要手动过滤空格
//ImageLoader为载体设置了唯一的标识符,意思其实就是每一个载体在某一个时刻应该只需要接受一个图片展示任务
//因为当前图片加载和展示是最新的请求,但是因为空,所以不需要开始任务,不过还有有必要要取消之前旧的请求
engine.cancelDisplayTaskFor(imageAware);
//回调开始加载状态
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//如果在DisplayOption中设置了空链接的情况下使用的资源图片,则尝试使用该资源图片
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
//回调加载结束状态,注意这里回调的Bitmap为null
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
//可以看到如果请求的链接为空,它会停止之前的请求,
//一个比较直接的结束之前请求并且设置想要的资源的方式可以采用这种方式
return;
}
//图片想要的尺寸,这个后期主要用于压缩处理的参数,默认都是null
if (targetSize == null) {
//这里简单理解就是载体的宽度
//有几种情况比较特殊
//在ImageView是wrap_content或者测量没有完成的情况下getWidth/Height为0、并且没有设置maxWidth的情况下
//使用配置的值或者屏幕的宽高,总之最后一定会有值
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
//获取缓存的key值,格式为链接_载体宽度x载体高度
//即同一个图片地址,对于不同大小的载体来说内存缓存是不同的,这点需要注意
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//记录当前请求的载体及其对于的内存缓存key值
//这个主要用于标记当前载体对应的请求,可以有效防止重复或乱序加载
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
//回调开始加载状态
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//首先从内存缓存中获取,默认配置的是LruMemoryCache,大小是当前可分配内存的1/8
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
//内存缓存命中
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
//如果需要对获得的Bitmap进行额外(拉伸之类的)操作,可以在DisplayOption中设置postProcessor
if (options.shouldPostProcess()) {
//初始化加载信息,就是设置一堆参数
//其中对每一个不同的URI都会拥有各自的ReentrantLock锁
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//内部分两步操作
//首先是获得对应的postProcess,并且执行对bitmap的操作
//然后就是开始执行展示bitmap的任务
//如果设置了syncLoading,即同步加载,那么整个流程都会在run的线程中进行
//否则会先进入子线程中执行postProcess,然后在指定的handler中执行展示任务(这个一般会有修改UI的操作,所以一般都是回到UI线程)
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {//同步执行,那么直接在displayImage的调用线程中运行任务
displayTask.run();
} else {//否则进入执行池的子线程中
engine.submit(displayTask);
}
} else {
//不需要对获得的bitmap做什么操作,直接展示并进行对于状态回调即可
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
//可以看到,如果击中内存的话则不会显示Loading中图片,这样做有一个主要的好处就是在ListView中滑动加载已经加载的图片的时候不会抖动
} else {//没有击中内存缓存
if (options.shouldShowImageOnLoading()) {//如果在DisplayImageOptions中指定Loading中资源图片,则直接展示Loading图片
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {//如果在DisplayImageOptions中没有指定Loading中资源图片
//如果此时在DisplayImageOptions中设置加载前需要重置载体中的图片资源,则尝试进行载体的图片资源重置
imageAware.setImageDrawable(null);
}
//设置加载中参数
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//加载图片,并且展示图片的任务
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {//同步,在当前线程执行
displayTask.run();
} else {//否则进入线程池中的子线程
engine.submit(displayTask);
}
}
}
/**
* 添加一个图片加载任务到执行池中,这里并不会自动展示图片。
* 而是会通过ImageLoadingListener的onLoadingComplete进行回调。
*
* @param targetImageSize 图片想要的目标大小,在一般的任务当中后续会进行压缩等处理,
* 最后再通过ImageLoadingListener的onLoadingComplete进行回调。
* 可以为null,则默认为当前屏幕的宽高
*
* ...重复的参数见displayImage
*/
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();//ImageLoader必须设置全局配置才可以使用
if (targetImageSize == null) {
//在全局配置当中可以通过设置memoryCacheExtraOptions指定图片的大小
//如果还不指定,则默认使用屏幕的宽高
targetImageSize = configuration.getMaxImageSize();
}
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
//内部基本就是一个空实现,不会进行setImageDrawable等操作
NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
//回到平时使用的加载图片的方法,但是因为一个空载体的原因,会导致请求图片成功,最多进行缓存,但不会自动显示
//可能说比较常用的就是预加载到缓存中的功能吧
displayImage(uri, imageAware, options, listener, progressListener);
}
/**
* 同步的加载图片方法
* 该方法最好在子线程中使用,因为这个过程中可能会有从网络上加载图片的过程
*
* ...参数可以见displayImage或者loadImage
*
* @return 图片的加载结果,即Bitmap。可以为null,意味着加载图片失败
*/
public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
//复制一份DisplayImageOptions并且设置isSyncLoading标志
//这个在displayImage中可以看到实际上要求在当前调用线程中执行图片的加载过程
options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();
//该回调会在onLoadingComplete记录最后获得的bitmap
//然后可以通过getLoadedBitmap获得对应的bitmap
SyncImageLoadingListener listener = new SyncImageLoadingListener();
//这个方法最后也是走displayImage,主要是不展示图片,然后将结果回调到SyncImageLoadingListener中
loadImage(uri, targetImageSize, options, listener);
//获得加载的结果,可能为null
//但是要注意,因为请求图片可能会从网络上获取,又是同步方法,则该方法最好在子线程中使用
//除非你能确定图片能从内存中获取,否则都是在子线程中合适
return listener.getLoadedBitmap();
}
一般来说常用的就以上三种,当然还有不少参数不同的重载方法,不过最终都是进过这个三个方法。
displayImage:一般用于异步加载图片,然后根据设置的配置展示和缓存图片。
loadImage:一般来说常用于预加载图片。
loadImageSync:如果当前在子线程,并且当前要求先加载图片,然后才能进行下一步操作,这个时候可以考虑这个方法。
接下来看一些常用的方法
/**
* 清空硬盘缓存
*/
public void clearDiskCache() {
checkConfiguration();
configuration.diskCache.clear();
}
/**
* 清空内存缓存
*/
public void clearMemoryCache() {
checkConfiguration();
configuration.memoryCache.clear();
}
/**
* 设置一个所有图片请求的默认请求监听
* 当请求图片的时候listener为null的时候默认使用
* */
public void setDefaultLoadingListener(ImageLoadingListener listener) {
defaultListener = listener == null ? new SimpleImageLoadingListener() : listener;
}
/**
* 尝试取消当前的展示任务
*
* @param imageAware 当前需要取消的请求对应的载体
*/
public void cancelDisplayTask(ImageAware imageAware) {
engine.cancelDisplayTaskFor(imageAware);
}
/**
* 拒绝或允许从网络上下载图片
*
* 如果网络被拒绝,那么图片不会进行缓存,然后会进行ImageLoadingListener的onLoadingFailed回调
* 并且指示FailReason为FailReason.FailType#NETWORK_DENIED
*
* @param denyNetworkDownloads true的话拒绝从网络上获取图片
* note:注意这个如果在图片已经获取ImageDownloader之后当次就无效了,只有之后的才会生效
*/
public void denyNetworkDownloads(boolean denyNetworkDownloads) {
engine.denyNetworkDownloads(denyNetworkDownloads);
}
/**
* 设置当前网络偏慢,http和https的请求将采用FlushedInputStream进行处理
*
* @param handleSlowNetwork true的话会使用FlushedInputStream处理网络的图片流
*/
public void handleSlowNetwork(boolean handleSlowNetwork) {
engine.handleSlowNetwork(handleSlowNetwork);
}
/**
* 暂停ImageLoader,后续需要执行的任务只有当resume()之后才会执行
*/
public void pause() {
engine.pause();
}
/**
* 尝试恢复那些暂停的任务,对应pause()
* */
public void resume() {
engine.resume();
}
/**
* 会尝试取消所有运行中或者等待运行的任务
* 注意如果使用的是自定义的线程池,这个方法会导致自定义线程池shutdown
* 但是后续ImageLoader依然可以使用,不过会使用ImageLoader默认的线程池
*/
public void stop() {
engine.stop();
}
/**
* stop当前ImageLoader,并且清空配置
* 后续可以通过重新init来重新使用ImageLoader
*/
public void destroy() {
if (configuration != null) L.d(LOG_DESTROY);
stop();
configuration.diskCache.close();
engine = null;
configuration = null;
}
稍微举几个例子来说明一下这几个方法的用处:
1.如果当前手机内存吃紧,当前app在后台可能会被回收,此时可以考虑通过clearMemoryCache释放内存缓存,从而降低app的内存使用率,从而降低被系统回收的可能。
2.如果app支持在wifi状态下才显示图片,当前网络状态监听到从wifi变为其它状态,此时可以考虑stop整个ImageLoader。当然这样做的话就需要通过判断configuration是否为空进行图片加载,需要一些额外的操作。
3.列表在滑动的时候,有的时候不希望fling快速滑动的过程中开启重复无用的任务,可以在滑动的时候pause,然后在滑动停止之后resume加载当前可见的图片。
ImageAware
充当载体的抽象,使用者可以通过不同的实现从而来达到不同的使用图片方式,这样的设计大大增加了灵活度。
先看接口的设计
public interface ImageAware {
/**
* 返回图片载体的宽度,实际上也是定义图片拉伸的大小。
* 可以返回0,如果当前无法获得宽度
* 如果在主线程调用ImageLoader,则该方法回调在主线程,否则在子线程
*/
int getWidth();
/**
* 返回图片载体的高度,实际上也是定义图片拉伸的大小。
* 可以返回0,如果当前无法获得高度
* 如果在主线程调用ImageLoader,则该方法回调在主线程,否则在子线程
*/
int getHeight();
/**
* 获得想要的图片的拉伸模式,一般会在拉伸和展示的时候有所区别
*/
ViewScaleType getScaleType();
/**
* 返回被装饰后的视图。
* 可能为null,如果没有视图被装饰或者说视图被回收了
*/
View getWrappedView();
/**
* 返回一个标志,用于告知ImageLoader当前载体是否有效,如果的当前载体被回收,那么
* ImageLoader应该停止对应的进行中的图片加载任务,并且进行ImageLoadingListener的onLoadingCancelled(String, View)回调
* 如果在主线程调用ImageLoader,则该方法回调在主线程,否则在子线程
*
* @return true表示当前视图已经被GC,ImageLoader不应该继续工作。
*/
boolean isCollected();
/**
* 返回当前载体对应的id。这个id应该是唯一的,同样的会对应于ImageLoader中的任务,对于同样id的载体来说,
* 不应该开始多个任务,而是会取消掉旧的任务,只执行最新的任务
* 这个有必要返回被装饰的view的hashCode(),这样可以避免因为视图复用导致展示错误的图片。
*/
int getId();
/**
* 用于设置加载并且处理后的drawable到指定的载体上面
* 下面是一些常用的方法:
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageForEmptyUri(
*android.graphics.drawable.Drawable) for empty Uri},
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnLoading(
*android.graphics.drawable.Drawable) on loading} or
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnFail(
*android.graphics.drawable.Drawable) on loading fail}. These drawables can be specified in
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions display options}.
* Also can be called in {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
* 如果在主线程调用ImageLoader,则该方法回调在主线程,否则在子线程
*
* @return true表示drawable成功设置
*/
boolean setImageDrawable(Drawable drawable);
/**
* 用于设置加载并且处理后的bitmap到指定的载体上面
* 可能通过该方法回调
* {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
* 如果在主线程调用ImageLoader,则该方法回调在主线程,否则在子线程
*
* @return true表示bitmap成功设置
*/
boolean setImageBitmap(Bitmap bitmap);
}
并没有通过泛型的设计,而是通过重载不同的方法来设置对应的bitmap或drawable之类。
因为实现类可以很多,这里只看ImageView的一些相关方法:
首先是ViewAware,作为ImageViewAware的父类
@Override
public int getWidth() {
View view = viewRef.get();
if (view != null) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
int width = 0;
//checkActualViewSize默认为true,当不是wrap_content的情况获取视图的宽度
if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
width = view.getWidth(); //这里要注意,如果视图第一次layout没有完成的话这个肯定是0咯,尽量在视图绘制完成后使用合适一点
}
if (width <= 0 && params != null) width = params.width;
//这里当视图绘制没有完成和wrap_content的时候可能为0或-2
return width;
}
return 0;
}
@Override
public View getWrappedView() {
return viewRef.get();
}
@Override
public boolean isCollected() {
return viewRef.get() == null;//通过弱应用持有的对象,如果没有其它引用的情况下,在系统GC的时候也会被回收
}
@Override
public int getId() {
View view = viewRef.get();//这里通过view的hashCode返回唯一标识符,可以应对视图复用的情况
return view == null ? super.hashCode() : view.hashCode();
}
@Override
public boolean setImageDrawable(Drawable drawable) {
//当前方法可能在UI线程或者子线程中回调
//此处只允许在UI线程中设置bitmap
if (Looper.myLooper() == Looper.getMainLooper()) {
View view = viewRef.get();
if (view != null) {//检查view是否被回收
setImageDrawableInto(drawable, view);//当前没有被回收
return true;
}
} else {
L.w(WARN_CANT_SET_DRAWABLE);
}
return false;
}
@Override
public boolean setImageBitmap(Bitmap bitmap) {
//当前方法可能在UI线程或者子线程中回调
//此处只允许在UI线程中设置bitmap
if (Looper.myLooper() == Looper.getMainLooper()) {
View view = viewRef.get();
if (view != null) {//检查view是否被回收
setImageBitmapInto(bitmap, view);//当前没有被回收
return true;
}
} else {
L.w(WARN_CANT_SET_BITMAP);
}
return false;
}
然后是ImageViewAware:
@Override
public int getWidth() {
int width = super.getWidth();
//1.视图绘制没有完成,并且没能通过View的getWidth获得宽度
//2.wrap_content的情况
if (width <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
//通过反射获取maxWidth属性,尝试用maxWidth来设置宽度
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
}
//这里还是可能为0
return width;
}
/**
* 获得ImageLoader支持的压缩类型,一共CROP和FIT_INSIDE两种
* 默认为CROP
*/
@Override
public ViewScaleType getScaleType() {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
return ViewScaleType.fromImageView(imageView);
}
return super.getScaleType();
}
@Override
public ImageView getWrappedView() {
return (ImageView) super.getWrappedView();
}
@Override
protected void setImageDrawableInto(Drawable drawable, View view) {
((ImageView) view).setImageDrawable(drawable);
if (drawable instanceof AnimationDrawable) {//如果是AnimationDrawable,比方说一个animation-list,尝试开启动画
((AnimationDrawable)drawable).start();
}
}
@Override
protected void setImageBitmapInto(Bitmap bitmap, View view) {
((ImageView) view).setImageBitmap(bitmap);
}
可以通过实现不同的载体来达到不同的效果。
比方说载体本身是一个FrameLayout,里面有一张图,加载完图片要展示,而且还要设置其它操作
或者说想要在展示图片的时候播放什么特殊的动画等等。
ImageLoaderConfiguration
ImageLoader要能够正常使用,必须要有唯一的ImageLoaderConfiguration,ImageLoaderConfiguration内部有Builder实现,可以相对方便的自定义一些参数,下面主要看一下参数的意义:
//用于确定图片的最大宽高,可以自定义,默认是屏幕的宽高
//当加载图片的时候没办法从ImageAware中获取有效的宽高的时候会使用
final int maxImageWidthForMemoryCache;
final int maxImageHeightForMemoryCache;
//如果同时没有击中内存/硬盘缓存
//如果DisplayOptions中允许缓存在硬盘中,并且尝试从网络、文件等方式获取图片成功后
//缓存入硬盘的Bitmap的最大宽高
final int maxImageWidthForDiskCache;
final int maxImageHeightForDiskCache;
//在已经满足maxImageWidthForDiskCache和maxImageHeightForDiskCache设置的图片缓存到硬盘中的最大宽高之后
//还可以在缓存到硬盘之前对bitmap进行额外操作
final BitmapProcessor processorForDiskCache;
//队列模式,LIFO表示后进先出,默认是先进先出(默认使用的是双向队列)
final QueueProcessingType tasksProcessingType;
//没有击中内存和硬盘缓存的时候,会通过该线程池尝试从网络等方向上加载并且处理图片
//基于threadPoolSize和threadPriority实现的一个fix线程池,也是Android常用的线程池
//有着固定的核心线程数和相同的最大线程数,并且队列长度无限
//即当线程池中执行线程数不足threadPoolSize的时候会新建线程执行,否则将任务放入队列中等待执行
final int threadPoolSize;
final int threadPriority;
final Executor taskExecutor;
//1.击中内存缓存,直接用于处理bitmap和展示bitmap的线程池,这个主要是处理自定义的postProcessor操作
//2.未击中内存缓存,但是击中硬盘缓存,会通过该线程池来获取图片并且进行处理,然后展示
//默认实现同taskExecutor
final Executor taskExecutorForCachedImages;
//当前taskExecutor是否使用了默认线程池
final boolean customExecutor;
//当前taskExecutorForCachedImages是否使用了默认线程池
final boolean customExecutorForCachedImages;
//内存缓存,允许自定义,默认是LruMemoryCache,size为JVM为当前进程分配的总内存量的1/8(如果在Application中开启了large_heap的话会更大)
final MemoryCache memoryCache;
//硬盘缓存,允许自定义,如果指定了最大的缓存大小或者缓存文件数目,默认是LruDiskCache,否则是UnlimitedDiskCache
final DiskCache diskCache;
//默认的加载图片的下载器
final ImageDownloader downloader;
//用于对图片进行压缩拉伸等操作
final ImageDecoder decoder;
//默认的展示图片配置
final DisplayImageOptions defaultDisplayImageOptions;
//拒绝进行网络请求的下载器
final ImageDownloader networkDeniedDownloader;
//网络慢的时候,通过FlushedInputStream来保证流的完全加载
final ImageDownloader slowNetworkDownloader;
//硬盘缓存的时候将uri转码的操作者,默认是HashCode的方式,常用的可能是MD5的编码模式
private FileNameGenerator diskCacheFileNameGenerator = null;
这里都是一些全局的配置,稍微留意一下自定义线程池的时候和stop的关系,stop之后自定义线程池会被shutdown并且重新使用默认线程池的问题。
一般的使用中可能有以下几种用途:
1.指定默认的DisplayImageOptions来统一一些占位图
2.自定义内存/硬盘缓存的大小
3.自定义图片在硬盘缓存上的文件名
DisplayImageOptions
展示图片的时候的配置,可以用于指定本次加载和展示图片的时候的一些行为,可以通过在ImageLoaderEngine中指定全局默认的,当然也可以在每一次加载的时候手动设置,从而覆盖全局默认的。
接下来看一些核心参数:
//如果没有命中内存缓存,在进行从硬盘之类的方式获取图片之前设置的图片
private int imageResOnLoading = 0;
private Drawable imageOnLoading = null;
//如果加载的图片地址为null或者“”的时候设置的图片
private int imageResForEmptyUri = 0;
private Drawable imageForEmptyUri = null;
//加载图片失败,可能是加载中出现一些异常,也可能最终获取的bitmap为宽高为0的时候设置的图片
private int imageResOnFail = 0;
private Drawable imageOnFail = null;
//在内存缓存没有命中的情况下,准备从硬盘和网络上获取图片的时候重置载体
private boolean resetViewBeforeLoading = false;
//是否要缓存到内存当中,如果为true,并且当次从网络或文件等形式获取bitmap不为null则会进行缓存
//这意味着哪怕是使用assets、drawable、content这一类都会进行缓存
private boolean cacheInMemory = false;
//是否要缓存到硬盘当中
//这个参数会改变一下流程。
//先假设获取的是网络图片
//首先必须要没有击中内存缓存,然后尝试从硬盘缓存中获取失败
//如果true,则会先从网络上加载图片,然后存入硬盘中,之后在从硬盘中读取获得bitmap
//false的话,直接从网络上读取获取bitmap
private boolean cacheOnDisk = false;
//decode图片时候使用的压缩方式,默认采用2乘,一直到bitmap的宽高都小于ImageAware的宽高
private ImageScaleType imageScaleType = ImageScaleType.IN_SAMPLE_POWER_OF_2;
private Options decodingOptions = new Options();
//击中内存缓存失败,然后进入加载任务通过线程沉睡的方式延迟加载
private int delayBeforeLoading = 0;
//是否要考虑图片的方向参数,这种只有在file://类型以及文件是.jpeg的时候为true才有用
//比方说照完相系统返回的图片,其他时候是没有作用的
private boolean considerExifParams = false;
//内存缓存之前可以对bitmap进行操作
private BitmapProcessor preProcessor = null;
//击中内存缓存后可以对bitmap进行操作,和preProcessor是一对
private BitmapProcessor postProcessor = null;
//用于展示Bitmap,默认实现就setImageBitmap,主要是通过这个实现一些淡入浅出等效果
private BitmapDisplayer displayer = DefaultConfigurationFactory.createBitmapDisplayer();
//在当前请求isSyncLoading为false的前提下,可以指定LoadAndDisplayImageTask最终执行的线程
//一般在UI线程中使用的时候不需要设置,默认就是主线程Handler
private Handler handler = null;
//是否同步加载,也就是将加载图片和displayImage执行在同一个线程中
private boolean isSyncLoading = false;
主要的意义看注释即可。
结语
这一篇只是开了一个头,基本上可以明确ImageLoader的使用
下一篇会进行详细流程分析,包括内存缓存、硬盘缓存、压缩处理等等