您的位置:首页 > 移动开发 > Android开发

Android-Universal-Image-Loader 学习笔记(二)加载图片原理

2015-11-11 11:24 591 查看
                                                       Android-Universal-Image-Loader
学习笔记(二)

1. 流程图

       了解一下UIL加载图片的流程(可以通过查看ImageLoader.displayImage(…)方法分析得出),网上一大把流程图,个人觉得比较容易理解为以下两个:



                                                           
    图一



                                                                                    图二

         从上图中,我们可以看出,UIL加载图片的一般流程是先判断内存中是否有对应的Bitmap,再判断磁盘(disk)中是否有,如果没有就从网络中加载。最后根据原先在UIL中的配置判断是否需要缓存Bitmap到内存或磁盘中。Bitmap加载完后,就对它进行解析,然后显示到特定的ImageView中。

二、加载图片

   Android-Universal-Image-Loader有五个重载方法:

public void displayImage(String uri, ImageAware imageAware) {
displayImage(uri, imageAware, null, null, null);
}

public void displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener) {
displayImage(uri, imageAware, null, listener, null);
}

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options) {
displayImage(uri, imageAware, options, null, null);
}

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener) {
displayImage(uri, imageAware, options, listener, null);
}

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener)


   第一个方法到第四个方法最终都是调用第五个方法,直接看第五个方法的参数:

    Uri:图片的Uri地址
    ImageAware:需要显示的ImageView
    Options: 显示图片的配置,没有就是用默认的
    ImageLoadingListenner:加载情况的监听,用户可以根据自己的情况重写
  
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 <b>null</b>.
* @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 <b>null</b>.
* @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 <b>null</b>.
*/
void onLoadingCancelled(String imageUri, View view);
}
 
     ImageLoadingProgressListener:加载进度条的情况,显示加载过程中的进度条百分比.
public interface ImageLoadingProgressListener {

/**
* Is called when image loading progress changed.
*
* @param imageUri Image URI
* @param view     View for image. Can be <b>null</b>.
* @param current  Downloaded size in bytes
* @param total    Total size in bytes
*/
void onProgressUpdate(String imageUri, View view, int current, int total);
}

   还有一个常用的优化ListView或者GridView的监听:

   ListView.setOnScrollListener(new PauseOnScrollListener(imageLoader,true,true));
    该方法用于设置滑动过程不加载图片,优化显示效果
    PauseOnScrollListener源码如下:
public class PauseOnScrollListener implements OnScrollListener {

private ImageLoader imageLoader;

private final boolean pauseOnScroll;
private final boolean pauseOnFling;
private final OnScrollListener externalListener;

/**
* Constructor
*
* @param imageLoader   {@linkplain ImageLoader} instance for controlling
* @param pauseOnScroll Whether {@linkplain ImageLoader#pause() pause ImageLoader} during touch scrolling
* @param pauseOnFling  Whether {@linkplain ImageLoader#pause() pause ImageLoader} during fling
*/
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {
this(imageLoader, pauseOnScroll, pauseOnFling, null);
}

/**
* Constructor
*
* @param imageLoader    {@linkplain ImageLoader} instance for controlling
* @param pauseOnScroll  Whether {@linkplain ImageLoader#pause() pause ImageLoader} during touch scrolling
* @param pauseOnFling   Whether {@linkplain ImageLoader#pause() pause ImageLoader} during fling
* @param customListener Your custom {@link OnScrollListener} for {@linkplain AbsListView list view} which also
*                       will be get scroll events
*/
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling,
OnScrollListener customListener) {
this.imageLoader = imageLoader;
this.pauseOnScroll = pauseOnScroll;
this.pauseOnFling = pauseOnFling;
externalListener = customListener;
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
imageLoader.resume();
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (pauseOnScroll) {
imageLoader.pause();
}
break;
case OnScrollListener.SCROLL_STATE_FLING:
if (pauseOnFling) {
imageLoader.pause();
}
break;
}
if (externalListener != null) {
externalListener.onScrollStateChanged(view, scrollState);
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (externalListener != null) {
externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}


       在调用diplayImage的时候,我们会判断pause状态.

三、从代码角度看ImageLoader加载显示过程

             从ImageLoader.displayImage(…)方法分析:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
//首先检测UIL的配置是否被初始化,在App的application中设置
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
// 设置一个默认的监听器  SimpleImageLoadingListener
listener = emptyListener;
}
if (options == null) {
// 当没有options,使用默认
options = configuration.defaultDisplayImageOptions;
}

if (TextUtils.isEmpty(uri)) {
//当uri是null,显示默认图片或者不显示
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
// 解析ImageView的大小,以便后面解析图片用
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);<span style="font-family: Arial; font-size: 14px; line-height: 26px;">//ImageLoaderEngine中存在一个HashMap,用来记录正在加载的任务</span>
listener.onLoadingStarted(uri, imageAware.getWrappedView());

// Bitmap 是否已经存在缓存
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
//Bitmap 存在cache缓存中,取出来用
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

if (options.shouldPostProcess()) {
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);
}
} else {
Log.d("wutingyou....", "ImageLoader from Cache");
// 显示图片,根据显示方式显示 默认SimpleBitmapDisplayer
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
// 回调完成接口
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
//Bitmap 不存在cache缓存中,从文件中或者网络里面获取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 {

eca5
engine.submit(displayTask);
}
}
}


           从上面的代码中,我们可以看出,它会将ImageView转换成ImageViewAware, ImageViewAware主要是做什么的呢?该类主要是将ImageView进行一个包装,将ImageView的强引用变成弱引用,当内存不足的时候,可以更好的回收ImageView对象,还有就是获取ImageView的宽度和高度。这使得我们可以根据ImageView的宽高去对图片进行一个裁剪,减少内存的使用。
        先把握整体的流程,我们先放弃细节方面的追踪。基本上重要的处理流程我都有用注释标出。不过,从这段代码中我们也可以看出这段代码的结构非常清晰。对图片的整个的加载流程都有对应的监听接口(ImageLoadingListener.onLoadingStarted,ImageLoadingListener.onLoadingComplete,ImageLoadingListener这个类就是用来监听图片的加载过程的),也就是说整个的图片加载过程程序员都可以进行相应的处理。我们先关注一下图片从无到有的加载过程,毕竟这部分是大家最为关心的。看到第63行中的LoadAndDisplayImageTask,跟进LoadAndDisplayImageTask.run()方法中。在这个run()方法中,除了 bmp
= tryLoadBitmap();这一句是对图片进行加载,其他的函数都是对Bitmap进行处理或者显示。我们继续进入看看

public void run() {
/**
* 如果这两者是true,直接返回
**/
// 这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )),
// pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling 控制猛的滑动ListView,GridView是否停止加载图片
if (waitIfPaused()) return;
if (delayIfNeed()) return;

ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}

loadFromUriLock.lock();
Bitmap bmp;
try {
checkTaskNotActual();
// 再次从cache内存缓存中
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
// cache缓存中没有或者已经被回收,尝试从sd卡缓存或者网络中获取
bmp = tryLoadBitmap();
if (bmp == null) return; // listener callback already was fired

checkTaskNotActual();
checkTaskInterrupted();

if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}

if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}

if (bmp != null && options.shouldPostProcess()) {
L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
checkTaskNotActual();
checkTaskInterrupted();
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
loadFromUriLock.unlock();
}
// 显示任务
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}


private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
// 尝试从磁盘缓存中读取Bitmap
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;

checkTaskNotActual();
// 解码图片
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
//没有缓存在磁盘,从网络中下载图片
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;

String imageUriForDecoding = uri;
//判断是否开启SD卡缓存,如果开启尝试缓存到SD上去
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}

checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);

if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}


          如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑, 接下来我们先看看waitIfPaused()

private boolean waitIfPaused() {
AtomicBoolean pause = engine.getPause();
if (pause.get()) {
synchronized (engine.getPauseLock()) {
if (pause.get()) {
L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
try {
engine.getPauseLock().wait();
} catch (InterruptedException e) {
L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
return true;
}
L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
}
}
}
// isViewCollected()是判断我们ImageView是否被垃圾回收器回收了,如果回收了,
// LoadAndDisplayImageTask方法的run()就直接返回了,
// isViewReused()判断该ImageView是否被重用,被重用run()方法也直接返回
return isTaskNotActual();
}


这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling 控制猛的滑动ListView,GridView是否停止加载图片

除此之外,这个方法的返回值由isTaskNotActual()决定,我们接着看看isTaskNotActual()的源码
private boolean isTaskNotActual() {
return isViewCollected() || isViewReused();
}

isViewCollected()是判断我们ImageView是否被垃圾回收器回收了,如果回收了,LoadAndDisplayImageTask方法的run()就直接返回了,isViewReused()判断该ImageView是否被重用,被重用run()方法也直接返回,为什么要用isViewReused()方法呢?主要是ListView,GridView我们会复用item对象,假如我们先去加载ListView,GridView第一页的图片的时候,第一页图片还没有全部加载完我们就快速的滚动,isViewReused()方法就会避免这些不可见的item去加载图片,而直接加载当前界面的图片.

         在tryLoadBitmap()判断磁盘中是否有缓存,就开始进行网络下载(tryCacheImageOnDisk())。在tryCacheImageOnDisk()函数中有个tryCacheImageOnDisk()的 loaded
= downloadImage()这行进行图片下载。
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

boolean loaded;
try {
//进行下载图片,并保存在SD卡中
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
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}

private boolean downloadImage() throws IOException {
// 根据网络状态选择下载器
// SlowNetWorkDownloader 慢速
// downloader 正常速度
// networkDeniedDownloader 网络拒绝
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
//利用Disk CaChe将Bitmap写入磁盘缓存
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}

    获取一个实现Image Downloader的downloader(当然这里,作者根据网络情况将downloader分为慢速(slowNetworkDownloader)、正常速度(downloader)、网络拒绝(networkDeniedDownloader)情况下的download,在这里我们不展开,你只要知道他们是imageDownloader接口的实现者就行),然后利用Disk
Cache将Bitmap写入磁盘缓存中。返回到之前我们进入downloadImage()函数中的tryLoadBitmap(),在将图片缓存到磁盘中。是否缓存到磁盘跟配置有关)后,紧接着调用bitmap
= decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));解析图片。进入decodeImage()函数中,我们发现UIL调用Image
Decoder进行图片的解析。

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

decode()函数最终是调用BaseImageDecoder.decode()方法进行解析的,这个利用之前获得的inputStream,直接从它身上读取数据,然后进行解析,并对整个下载任务的网络接口进行重置。

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


接下来,有了解析好的Bitmap对象后,剩下的就是在Image View对象中显示它了。我们回到文章一开始介绍到的ImageLoader.displayImage(...)函数中。

       我们进去DisplayBitmapTask.run()函数中看看。除去前面几行的ImageLoadingListener.ImageLoadingListener()代码,相关代码其实就一行 displayer.display(bitmap,
imageAware, loadedFrom),它其实就是调用BitmapDisplayer这个对象将Bitmap对象显示到ImageView上。根据实现BitmapDisplayer接口的不同对象,还有SimpleBitmapDisplayer、FadeInBitmapDisplayer、RoundedBitmapDisplayer等对象.

      
public void run() {
if (imageAware.isCollected()) {
// ImageView已经被回收
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
// ImageView已经被重用
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);
}
}
            最后可以再次看看上面的流程图。。。今天记录到此。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 异步 bitmap