ImageLoader图片加载流程分析

ImageLoader加载图片的方法有两种:
1.loadImage
2.displayImage
经过查看源码,发现其实两种加载方法最终是一样的,因为loadImage方法最终也是调用的displayImage方法。代码如下:


ImageLoader图片加载流程分析_第1张图片

所以分析ImageLoader加载图片流程时,只需分析displayImage()方法就OK了。
下面就开始分析displayImage的加载图片流程。


ImageLoader图片加载流程分析_第2张图片

通过上图代码,我们可以看到,首先是检查一些必要的参数。

第一步,第236行首先通过checkConfiguration()检查Configuration参数。从图中可以看到,如果Configuration参数为空,会抛出初始化异常,说明在调用加载图片之前没有进行初始化。


ImageLoader图片加载流程分析_第3张图片

第二步,然后从237行到245行是在检查我们方法中传入的参数是否为空。如果imageAware为空,直接抛出异常。但如果是listener这样的参数为空,就会使用ImageLoader初始化时配置的参数。从这里我们可以看出如果我们在调用方法时传入了自己的DisplayImageOptions、ImageLoadingListener参数,就会使用传入的参数,否则会使用初始化时候配置的参数,也就是说我们传入的参数是具有优先权的。

检查完毕参数,接下来就是该根据url来加载图片来。先截图代码。


ImageLoader图片加载流程分析_第4张图片

ImageLoader图片加载流程分析_第5张图片

ImageLoader图片加载流程分析_第6张图片

ImageLoader图片加载流程分析_第7张图片

一、 首先从247行到257行,是检查我们的图片url是否为空。在为空的情况下,会先取消正在为这个imageview加载图片的任务(可能是上次为这个imageview加载不同url的任务)。接着会调用ImageLoadingListener的onLoadingStarted方法,表示加载图片开始了。然后,查找我们在Configuration中是否配置了图片地址为空时显示的图片,如果配置了,就显示配置的图片,否则设置显示null.最后调用ImageLoadingListener的onLoadingComplete方法,这次加载图片就愉快的完成了!

二、下面我们再来看url不为空的情况,分为以下几个步骤

1、首先,映入眼帘的是String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);这行代码根据url和对应的imageview的大小,生成一个对应的key值,这个key将来作为从内存缓存中存取图片的依据。

2、然后,执行这句。listener.onLoadingStarted(uri, imageAware.getWrappedView());

3、第267行,用上一步生成的key从内存缓存中取对应的图片。如果图片不为空并且没有被回收时,那么我们就可以复用这个图片了。取到需要的图片后,就可以放到对应的imageview上了吗?不要急,中间还要经过一些判断来决定获取到图片是否需要进行处理。请看代码第271行,判断我们是否设置了获取到图片后进行进行额外的处理(比如圆角处理)。如果不需要,那么只需要执行这两句代码options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp); 到此为止,图片加载就算完成了。如果需要,就封装一个ProcessAndDisplayImageTask的任务,进行图片处理。这里需要注意的是,如果我们设置的是同步加载图片,可以直接执行。否则需要提交到线程中进行处理。那么ProcessAndDisplayImageTask到底进行了什么操作呢?

@Override
public void run() {
    L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);

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

从代码中可以看到,先是调用了我们在配置文件中传进去的BitmapProcessor对图片进行处理,处理完之后交给LoadAndDisplayImageTask显示图片。那么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);
    }
}

从代码中看到,首先判断了imageview是否被回收,毕竟我们中间经过了处理图片这个步骤,所以要判断在此期间imageview是否被回收了。同样还要判断一下这个imageview是否是被别的图片给用了。比如在listview中快速滑动图片,可能同一个imageview已经被加载了好几个不同的图片,那么最终显示的肯定是最后一次加载的图片,以前的都要废掉。如果前两种情况都没有出现,那么imageview要显示的就是我们现在获取的这张图片了。到此为止,直接从内存中加载图片的流程就走完了。

4、下面该介绍内存缓存中不存在图片的情况了。
从上面图片代码的286行开始到最后,就是加载图片的全部了。
(1)判断是否设置了正在加载中的默认图片,如果设置了就先将图片设置为我们配置文件中的加载中图片。如果没有设置,再判断是否需要重置imageview,如果需要就将imageview的图片置为空(因为在加载新的图片过程中,imageview还显示着之前的旧图片)。从这里我们可以看出在两种设置都存在的情况下,设置默认加载图片优先于置空操作,且两种只执行一种。
(2)接下来就该去加载图片了。加载图片的具体内容在LoadAndDisplayImageTask中。来看LoadAndDisplayImageTask中的代码。


ImageLoader图片加载流程分析_第8张图片

ImageLoader图片加载流程分析_第9张图片

ImageLoader图片加载流程分析_第10张图片

(3)先是判断加载线程是否中断,然后判断是否延迟加载,如果延迟,延迟操作后再返回判断结果(在判断中已经把延迟执行完毕了)
(4)加锁
(5)判断imageview是否被回收和被其他图片占用,如果是,抛出异常,结束加载图片。如果不符合上面的条件,那么就可以加载图片了。bmp = tryLoadBitmap();这句就是正真的从网络或者文件加载图片的代码,我们在下面再详细介绍,现在先介绍加载完图片后做的操作。加载完成后,如果图片不为空,并且imageview没有被回收、复用,并且线程没有被中断的情况下,判断配置中是否需要对图片进行预处理(就是保存到内存之前进行处理),如果需要就用配置中传进来的图片处理器对图片进行处理。然后判断是否需要保存到内存中并进行保存。
(6)接下来的操作就和从内存中取出图片后的操作相似了。

5、tryLoadBitmap()方法,从本地文件或者内存中加载图片


ImageLoader图片加载流程分析_第11张图片

ImageLoader图片加载流程分析_第12张图片


(1)从上面代码上可以看出,首先是从文件缓存中去取图片。如果能取到,经过bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));处理后,就可以返回图片了。
(2)本地缓存文件不存在时,会判断是否需要缓存在本地,如果需要会执行tryCacheImageOnDisk()方法。在这个方法中会根据uri的类型去加载图片。具体类型为下:

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

获取到图片后,会通过resizeAndSaveImage方法对图片做一些处理,保存到缓存文件中。从这里可以看出,即时我们加载的是资源文件或者本地文件,这些图片也会在我们的缓存文件中再存储一份。在这里注意第233行代码,将imageUriForDecoding的类型设置成了FILE类型,而imageUriForDecoding 的初始值是uri。这在下面将会用到。前面介绍的是需要缓存在本地的情况,那么不需要的情况下,将不会执行上面的一系列操作。所以看238行,又一次执行了bitmap = decodeImage(imageUriForDecoding);这里,如果执行了上面的操作,imageUriForDecoding就是FILE类型,直接从缓存文件中去就行了,如果没有,还需要根据原来的类型来加载图片。

我们来看一下decodeImage()到底是怎么获取到图片的。

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

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

protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
    return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}

从代码中发现,显示执行了decoder.decode(decodingInfo),然后又调用到getImageStream()方法.这个方法上面也贴出来了,通过这个方法获取到输入流转换成了图片。

到此为止,整个加载图片的流程就完了。

你可能感兴趣的:(ImageLoader图片加载流程分析)