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