Android平台下有一个著名的图片加载框架叫UniversalImageLoader,这个框架经过几次重构才有今天,有良好的扩展性。如果你曾经在ListView中加载图片出现过图片错乱,如果你曾经因加载图片过多而遇到OOM,那么你应该考虑使用UniversalImageLoader了。
UniversalImageLoader支持二级缓存,内存+磁盘缓存,图片一旦从网络下载后会保存在磁盘缓存里并缓存到内存,这样下次就不会去网络上重新下载,并且内存中如果存在就不会去磁盘上加载,在有限的内存设备上,这种方法增加了内存的换入换出操作,但是总的来说比烦人的OOM要好得多。
本人使用的版本 Version name 1.9.4 Version code 39
为了能够看懂UniversalImageLoader的源码,以下内容你必须知道怎么回事,否则读源码会有困难。本文假定读者有基本的Android控件编程能力。
1) Java多线程的 Executor, 以及ExecutorService, WeakReference如何使用(网上自己去查)
2) 如何使用 UniversalImageLoader(Sample code里有)
解读源码是一件很痛苦的事,因为有时候我们明明知道整个项目是如何工作的,结构如何等等,但是真要是说明白却不知道从哪里下手,在此本人试图说清,欢迎拍砖
下面从包的级别开始,从外层到最核心的代码
com.nostra13.universalimageloader.core.imageaware 封装了图片组件,它将使用图片的控件抽象成 ImageAware,
ViewAware实现了ImageAware接口, ImageViewAware继承ViewAware, NonViewAware也实现了ImageAware接口 ViewAware的接口定义如下:
/* 理解UniversalImageLoader最好的方式就是先去搞清楚如何使用,然后去看每个包下面的接口,
因为UIL充分利用了接口进行抽象,封装了变化,实现了很好的可扩展性。为了方便说明,这里假定是ImageView的一个抽象,定义非常简单*/
public interface ImageAware {
int getWidth(); //获取ImageView的宽度
int getHeight(); //获取ImageView的高度
ViewScaleType getScaleType();//获取ImageView的缩放类型
View getWrappedView();// 这个就是组件包装的View,通常是个ImageView,当然也可以是其他的View
boolean isCollected();// 判断View是否被回收,后面会解释
int getId();
boolean setImageDrawable(Drawable drawable);// 给包装的View设置图片
boolean setImageBitmap(Bitmap bitmap);} // 给包装的View设置图片
/* 这个抽象类实现了大部分方法,而把设置图片的工作留给子类*/
public abstract class ViewAware implements ImageAware {
public static final String WARN_CANT_SET_DRAWABLE = "Can't set a drawable into view. You should call ImageLoader on UI thread for it.";
public static final String WARN_CANT_SET_BITMAP = "Can't set a bitmap into view. You should call ImageLoader on UI thread for it.";
protected Reference<View> viewRef;//这就是为什么isCollected方法的由来
protected boolean checkActualViewSize;
public ViewAware(View view) {
this(view, true);
}
public ViewAware(View view, boolean checkActualViewSize) {
if (view == null) throw new IllegalArgumentException("view must not be null");
this.viewRef = new WeakReference<View>(view);//采用弱引用包装,这样做会为Bitmap的缓存提供方便,如果被回收了,那么Bitmap或许没必要缓存了
this.checkActualViewSize = checkActualViewSize;//尽量获取实际的View宽高,是尽量,如果获取不到,那就取布局参数的宽高
}
@Override
public int getWidth() {
View view = viewRef.get();
if (view != null) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
int width = 0;
if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
width = view.getWidth(); // Get actual image width
}
if (width <= 0 && params != null) width = params.width; // Get layout width parameter
return width;
}
return 0;
}
@Override
public int getHeight() {
View view = viewRef.get();
if (view != null) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
int height = 0;
if (checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
height = view.getHeight(); // Get actual image height
}
if (height <= 0 && params != null) height = params.height; // Get layout height parameter
return height;
}
return 0;
}
@Override
public ViewScaleType getScaleType() {
return ViewScaleType.CROP;
}
@Override
public View getWrappedView() {
return viewRef.get();// 可能为null
}
@Override
public boolean isCollected() {
return viewRef.get() == null;
}
@Override
public int getId() {
View view = viewRef.get();
return view == null ? super.hashCode() : view.hashCode();
}
@Override
public boolean setImageDrawable(Drawable drawable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
View view = viewRef.get();
if (view != null) {
setImageDrawableInto(drawable, view);
return true;
}
} else {
L.w(WARN_CANT_SET_DRAWABLE);
}
return false;
}
@Override
public boolean setImageBitmap(Bitmap bitmap) {
if (Looper.myLooper() == Looper.getMainLooper()) {
View view = viewRef.get();
if (view != null) {
setImageBitmapInto(bitmap, view);
return true;
}
} else {
L.w(WARN_CANT_SET_BITMAP);
}
return false;
}
protected abstract void setImageDrawableInto(Drawable drawable, View view);
protected abstract void setImageBitmapInto(Bitmap bitmap, View view);
}