因为情怀,所以很想研究下这个框架...
先梳理一下Java知识
在Java里, 当一个对象o被创建时, 它被放在Heap(堆内存)里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件:
1)没有任何引用指向它
2)GC被运行.
在现实情况写代码的时候, 我们往往通过把所有指向某个对象的referece置空来保证这个对象在下次GC运行的时候被回收
为了减少咱们开发者去手动置空对象,Java中引入了主要的WeakReference和SoftReference
弱引用特性:
**当一个对象仅仅被WeakReference指向, 而没有任何其他StrongReference指向的时候, 如果GC运行,不管当前内存空间足够与否, 那么这个对象就会被回收. **
软引用特性:
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存.
好了,有了这些概念,可以向下看了!
先看看加载图片主入口
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
imageAware是一个包装类ImageViewAware对象,父类是ViewAware,可以看到它内部维护的是一个ImageView的弱引用对象:
public ViewAware(View view, boolean checkActualViewSize) {
if (view == null) throw new IllegalArgumentException("view must not be null");
this.viewRef = new WeakReference(view);
this.checkActualViewSize = checkActualViewSize;
}
ImageSize是一个对宽度和高度的包装
加载过程:
1.如果uri地址为空,那么ImageLoaderEngine取消显示任务
void cancelDisplayTaskFor(ImageAware imageAware) {
cacheKeysForImageAwares.remove(imageAware.getId());
}
并将空图片设置给ImageView或者不显示图片,任务显示完成,返回return
2.地址不为空,继续向下执行,首先检测尺寸大小,如果大小未指定就设置屏幕的尺寸
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
3.继续向下执行,生成缓存key,并将imageAware和key一一对应关系缓存起来
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
这个key是地址和大小的拼凑
Pattern for cache key - [imageUri]_[width]x[height].
4.继续向下,根据key从内存缓存中取bitmap
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
而这个memoryCache是初始化ImageLoaderConfiguration的时候配置的,它在什么时候被实例化的呢?
UIL初始化的时候是这么用的
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.memoryCacheExtraOptions(480, 800) // default = device screen dimensions
.diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)
.taskExecutor(...)
.taskExecutorForCachedImages(...)
.threadPoolSize(3) // default
.threadPriority(Thread.NORM_PRIORITY - 1) // 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)
.diskCacheFileCount(100)
.build();
是不是建造者模式,在build()的时候
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
初始化空值检测
if (memoryCache == null) {
memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
}
如果为空,也就是初始化的时候没有指定实例对象,那么就赋值为默认的内存缓存对象,工厂模式来了
public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
if (memoryCacheSize == 0) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
if (hasHoneycomb() && isLargeHeap(context)) {
memoryClass = getLargeMemoryClass(am);
}
memoryCacheSize = 1024 * 1024 * memoryClass / 8;
}
return new LruMemoryCache(memoryCacheSize);
}
原来默认是LruMemoryCache缓存,默认大小是1/8的可用内存空间,似乎这已经成为行业标准!
UIL框架一共提供了8种缓存策略,现在知道LruMemoryCache是默认的,它内部维护的是一个
LinkedHashMap map
容器,
map = new LinkedHashMap(0, 0.75f, true);
每次访问的时候都会重新排序,把accessOrder设为true,也就是基于访问顺序 排序,每次访问get或者put,都会把最不常用的移到表头,如果超过了内存限制,会把最不常访问的那个删掉,也就是从表头移除remove
这里从内存缓存中拿出了bitmap
1.如果不为空,没有被回收
1.1 然后判断options.shouldPostProcess(),是否需要额外处理bitmap
如果为true,判断是同步还是异步
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
ProcessAndDisplayImageTask是一个Runnable 对象,如果是异步操作的话,这个任务会被分配到线程池中执行taskExecutorForCachedImages.execute(task);
看一下线程池的初始化
private Executor createTaskExecutor() {
return DefaultConfigurationFactory
.createExecutor(configuration.threadPoolSize, configuration.threadPriority,
configuration.tasksProcessingType);
}
线程池的大小,优先级执行策略的初始值,默认最多3个线程同时,先进先出的任务调度原则
private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
private int threadPriority = DEFAULT_THREAD_PRIORITY;
private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
public static final int DEFAULT_THREAD_POOL_SIZE = 3;
public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 2;
工厂生产线程池执行器过程:
public static Executor createExecutor(int threadPoolSize, int threadPriority,
QueueProcessingType tasksProcessingType) {
boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
BlockingQueue taskQueue =
lifo ? new LIFOLinkedBlockingDeque() : new LinkedBlockingQueue();
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
createThreadFactory(threadPriority, "uil-pool-"));
}
这里队列区分了后进先出LIFO这个特殊模式!
回到异步执行,run方法执行
@Override
public void run() {
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
processor加工处理完bitmap后,交给显示任务LoadAndDisplayImageTask显示
@Override
public void run() {
if (imageAware.isCollected()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
displayer.display(bitmap, imageAware, loadedFrom);
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
如果正常显示,是交给了displayer这个显示器,而这个显示器默认也是配置工厂生产的
public static BitmapDisplayer createBitmapDisplayer() {
return new SimpleBitmapDisplayer();
}
字面理解就是最简单的显示器,看看它的内容
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}
直接imageview显示bitmap
BitmapDisplayer是父类,那么一共有5种显示器,还有圆形的,圆角的,渐变的等显示器,比较丰富!
1.2回到options.shouldPostProcess(),如果为false,也就是不需要处理从内存中取出的bitmap,那么就直接交给显示器显示
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
2.如果从内存中取出的bitmap为空或者已经被回收了呢?继续向下看码
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
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);
}
首先从配置信息中判断是否需要在加载的时候显示自定义加载图片,为了体验更好通常会显示Loading的Icon,好了,进入核心代码,准备从本地磁盘或者网络下载图片 ,这里和上面一样,也是区分同步还是异步操作
2.1 如果是同步的,直接执行run()方法
一行行的看
if (waitIfPaused()) return;
if (delayIfNeed()) return;
首先判断一些状态,有这么几种情况,会返回
(1)如果任务被中断了interrupted
(2)如果ImageAware被GC回收了
(3)如果image的地址和任务的地址不一致
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
loadFromUriLock.lock();
获取锁,从内存中获取bitmap
bmp = configuration.memoryCache.get(memoryCacheKey);
如果bmp是不可用的,加载本地图片
bmp = tryLoadBitmap();
File imageFile = configuration.diskCache.get(uri);
首先从硬盘中取图片文件,如果没有的话tryCacheImageOnDisk(),就尝试去网络下载然后缓存到磁盘上,跟进去
loaded = downloadImage();
if (loaded) {
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
resizeAndSaveImage(width, height); // TODO : process boolean result
}
}
private boolean downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
拿到流后给diskCache硬盘缓存保存,默认的文件名生成器是HashCodeFileNameGenerator 默认的磁盘缓存器是UnlimitedDiskCache,无限制的缓存
缓存目录:/Android/data/[app_package_name]/cache
Hash文件名生成
public class HashCodeFileNameGenerator implements FileNameGenerator {
@Override
public String generate(String imageUri) {
return String.valueOf(imageUri.hashCode());
}
}
这里下载缓存到本地之后,resizeAndSaveImage执行,把bitmap从刚才缓存的文件中读上来,调整大小,然后二次保存!
好,disk缓存工作全部结束,然后要读图片,加载到内存并且显示
bitmap = decodeImage(imageUriForDecoding);
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
这里包装了一个ImageDecodingInfo对象,倒数第二个参数是一个下载器对象,跟进去看看
private ImageDownloader getDownloader() {
ImageDownloader d;
if (engine.isNetworkDenied()) {
d = networkDeniedDownloader;
} else if (engine.isSlowNetwork()) {
d = slowNetworkDownloader;
} else {
d = downloader;
}
return d;
}
networkDeniedDownloader指向的是NetworkDeniedImageDownloader,它是一个装饰器Decorator(包裹了ImageDownloader对象), 从1.8.0版本开始有了这个类,目的是为了阻止从网络下载图片,看看它的getStream()方法,根据加载地址判断,如果是http和https开头,就直接抛出异常了,那么只能从本地资源加载了!
slowNetworkDownloader指向的是SlowNetworkImageDownloader,同样是一个装饰器Decorator,它为了处理比较慢的网络情况,如访问
http://code.google.com/p/android/issues/detail?id=6066这样的地址
downloader可以由开发者外部定义传入,或者是默认的,BaseImageDownloader这个对象,好了3种下载器都介绍了!继续往下看!
解码器decoder要工作了decoder.decode(decodingInfo)
这个decoder可以开发者从外部定义传入,默认值是BaseImageDecoder
跟进去,看如何decode的
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = resetStream(imageStream, decodingInfo);
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
首先是获得网络流InputStream,这里只分析默认的情况BaseImageDownloader下载器
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
根据地址区分图片的源头,分析网络流
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
if (!shouldBeProcessed(conn)) {
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
这里如果连接不能建立的话,会重连5次,达到5次就不继续连接了,这里网络用的是HttpURLConnection,最后返回ContentLengthInputStream这个流,又是一个包装类,继承自InputStream, 这个流是从1.9.1版本开始有的,她的解释是可以获取自定义长度的流,她通过重写int available()
方法来限制读取length长度的内容,而这个长度可以看到是conn.getContentLength(),也就是文件大小
继续看码,拿到流了,定义文件大小和图片旋转方向
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
补充一句获取bitmap的时候尽量用BitmapFactory.decodeStream而不要用decodeFile\decodeResource之类的
protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
throws IOException {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(imageStream, null, options);
ExifInfo exif;
String imageUri = decodingInfo.getImageUri();
if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
exif = defineExifOrientation(imageUri);
} else {
exif = new ExifInfo();
}
return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
}
ExifInfo保存了图片的一些旋转、翻转等信息,接下来因为已经读了一次流InputStream取到了图片大小信息,后面还要读流,怎么办呢?
答案是重置流resetStream
if (imageStream.markSupported()) {
try {
imageStream.reset();
return imageStream;
} catch (IOException ignored) {
}
}
IoUtils.closeSilently(imageStream);
return getImageStream(decodingInfo);
首先判断流是否支持标记mark,如果支持就可以调用reset()方法重置,默认的InputStream是不支持的,也就是只能读一次,输入管道内容就没了,但是当前这个流是
ContentLengthInputStream这个包装对象,它重写了markSupported
@Override
public boolean markSupported() {
return stream.markSupported();
}
退回到获取流的时候,最后ContentLengthInputStream包裹的是BufferedInputStream缓冲输入流,而她是支持reset()的
当然如果包裹的是其它不支持标记的流,这里 往下执行,就要重新建立连接获得流对象了,getImageStream(decodingInfo)再次完成建立连接,获取Inputstream!
此时就可以通过流去获取bitmap对象了
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions)
拿到原始bitmap还不能马上返回,还要根据上面获得的图片大小,旋转,翻转等信息,看看bitmap是否需要二次加工,涉及Matrix矩阵变换...
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal)
加工完成后bitmap返回,继续回到LoadAndDisplayImageTask的run方法里,向下执行
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
这里判断一下是否需要缓存到内存
最后一步
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
启动了一个任务,交给显示器displayer显示
public void run() {
...
displayer.display(bitmap, imageAware, loadedFrom);
}
基本上一个请求的过程就算分析完了,最后看一下整体流程图
这里下载器,解码器,Bitmap处理器,显示器都可以自己制定.