[源码分析]ImageLoader加载图片

ImgeLoader在加载图片时,常用的方法就是displayImage方法,displayImageImageLoader.java中,该类的多个重载的displayImage的方法,从各方法中可以看到,最终都是调用的都是这个方法:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
    checkConfiguration();
    if (imageAware == null) {
        throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
    }
    if (listener == null) {
        listener = defaultListener;
    }
    if (options == null) {
        options = configuration.defaultDisplayImageOptions;
    }

    if (TextUtils.isEmpty(uri)) {
        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;
    }

    if (targetSize == null) {
        targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
    }
    String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
    engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

    listener.onLoadingStarted(uri, imageAware.getWrappedView());

    Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
    if (bmp != null && !bmp.isRecycled()) {
        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 {
            options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
        }
    } else {
        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);
        }
    }
}

接下来一步步从这入口开始,顺藤摸瓜,找到实现原理。
首先看看checkConfiguration();

/**
 * Checks if ImageLoader's configuration was initialized
 *
 * @throws IllegalStateException if configuration wasn't initialized
 */
private void checkConfiguration() {
    if (configuration == null) {
        throw new IllegalStateException(ERROR_NOT_INIT);
    }
}

这里主要是对初始化的检查,这也就是为什么在使用使用ImageLoader前,需要调用ImageLoader.getInstance().init(builder.build())的原因。

public synchronized void init(ImageLoaderConfiguration configuration) {
    if (configuration == null) {
        throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
    }
    if (this.configuration == null) {
        L.d(LOG_INIT_CONFIG);
        engine = new ImageLoaderEngine(configuration);
        this.configuration = configuration;
    } else {
        L.w(WARNING_RE_INIT_CONFIG);
    }
}

接下来再跟着displayImage往下走,看到有一代码段:

if (TextUtils.isEmpty(uri)) {
    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;
}

这里判断,如果地址为空,就会根据传入的options,显示的默认图片。
接下来就是加载图片的主要代码段了:

if (targetSize == null) {
    targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

listener.onLoadingStarted(uri, imageAware.getWrappedView());

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
    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 {
        options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
        listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
    }
} else {
    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);
    }
}

首先,ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize())会计算控件所需要的宽度、高度,然后生成一个ImageSize。defineTargetSizeForView方法内容比较简单,可以粗略过一下:

public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
    int width = imageAware.getWidth();
    if (width <= 0) width = maxImageSize.getWidth();

    int height = imageAware.getHeight();
    if (height <= 0) height = maxImageSize.getHeight();

    return new ImageSize(width, height);
}

后成ImageSize后,根据这个ImageSize,找出memoryCache,如果存在内存缓存,则直接加载缓存,显示图片,具体显示方法,在后续再做详细研究,图片第一次加载时,内存中是没有缓存的,所以跟着看else下的代码:

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
    ...
} else {
    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);
    }
}

可以看到,把图片的加载信息,包装成一个ImageLoadingInfo,并定义了一个LoadAndDisplayImageTask,传入相关配置信息,LoadAndDisplayImageTask实现了Runnable,在这里就启动了这个task,接下来去LoadAndDisplayImageTask类找到它的run方法,进行下一步研究。
粗略的看一下run,发现它是判断内存中没有这个图片,再进行加载、显示,加载完成后,再判断是否缓存内存,把run方法精简一下:

@Override
public void run() {
    ...
    try {
        checkTaskNotActual();

        bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp == null || bmp.isRecycled()) {
            bmp = tryLoadBitmap();

            ...

            if (bmp != null && options.isCacheInMemory()) {
                L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                configuration.memoryCache.put(memoryCacheKey, bmp);
            }
        }    
        ...
    } catch (TaskCancelledException e) {
        ...
    } finally {
        loadFromUriLock.unlock();
    }

    DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
    runTask(displayBitmapTask, syncLoading, handler, engine);
}

跟着进入tryLoadBitmap(),简化一下,首先从磁盘中读取这个文件的缓存,如果没有的话,再加载图片,有两个入口:

1、options.isCacheOnDisk() && tryCacheImageOnDisk(),如果配置需要保存磁盘缓存时,从tryCacheImageOnDisk()加载图片
2、decodeImage(imageUriForDecoding)直接加载

private Bitmap tryLoadBitmap() throws TaskCancelledException {
    Bitmap bitmap = null;
    try {
        File imageFile = configuration.diskCache.get(uri);
        if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {            
            ...         
            bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
        }
        if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
            ...
            if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                
            }           
            ...         
            bitmap = decodeImage(imageUriForDecoding);
            ...
        }
    } catch (IllegalStateException e) {
        ...
    }
    return bitmap;
}

先看一下tryCacheImageOnDisk方法,主要调用的是downloadImage,然后由调用downloadImage调用getDownloader()获取到文件流

private boolean tryCacheImageOnDisk() throws TaskCancelledException {
    ...
    try {
        loaded = downloadImage();
        ...
    } catch (IOException e) {
        ...
    }
    return loaded;
}

private boolean downloadImage() throws IOException {
    InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
    ...
            return configuration.diskCache.save(uri, is, this);
}

再看一下decodeImage方法,主要流程也差不多,先声明一个ImageDecodingInfo包装好相关信息,再通过decode方法加载。

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

关于decoder,在初始化ImageLoader时,可以通过ImageLoaderConfiguration.Builder(context).imageDecoder()自定义一个,否则会通过DefaultConfigurationFactory.createImageDecoder(writeLogs);默认一个,也就是new一个BaseImageDecoder,跟进BaseImageDecoder.decode()方法看一下源码,主要是调用了getImageStream方法,同时,可以看到getImageStream最终也是通过getDownloader()获取的文件流。

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

这样,这两个入口,最终调用的放法都是一样的:getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader()),这个getDownloader()又是何方神圣呢?

在初始化ImageLoader时,可以通过ImageLoaderConfiguration.Builder(context).imageDownloader()配置一个自定义的Downloader,自定义自己的下载处理逻辑,当然了,如果不进行配置,也会通过DefaultConfigurationFactory.createImageDownloader(context);生成一个默认的Downloader,也就是BaseImageDownloader,我们可以看从BaseImageDownloader.getStream()中,可以先看看默认的加载方式。

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

这个方法里,主要是判断这个地址的类型,网络图片、本地文件、本地资源等处理方式,用于加载不同的文件流,取到文件流后转成bitmap,就能放到控件上进行显示。
至此,整个下载图片过程就结束。
加载完成后是怎么显示的,回到LoadAndDisplayImageTask.run方法,可以看到,最后代码段:

DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

该段代码定义了一个DisplayBitmapTask,通过把加载信息包装传入处理,DisplayBitmapTask也是一个实现了Runnable的类,接下来可以看一下DisplayBitmapTask.run()方法。

@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.display()进行加载显示,这里的displayer是在最开始的入口ImageLoader.displayImage()中,通过options中传入的,它的类型为DisplayImageOptions,也就是我们每次使用时,可以新建的options,这里可以通过它设置类似圆角图片等等,另外,在ImageLoaer初始化init时,可以通过ImageLoaderConfiguration.Builder(context).defaultDisplayImageOptions()设置一个默认的,但如果没有设置的话,默认情况下也会通过DefaultConfigurationFactory.createBitmapDisplayer()创建一个默认的,也就是SimpleBitmapDisplayer,可以看看SimpleBitmapDisplayer的代码,非常简单,就是设置一下bitmap。

public final class SimpleBitmapDisplayer implements BitmapDisplayer {
    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        imageAware.setImageBitmap(bitmap);
    }
}

再回过头来,研究一下最开始的入口,在ImageLoader.displayImage()方法中,如果内存缓存中有,会走如下代码:

if (bmp != null && !bmp.isRecycled()) {
    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 {
        options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
        listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
    }
} 

可以看到这里是生成了一个ProcessAndDisplayImageTask并执行了,跟进这个类的run方法看一下:

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

发现它最终走的还是DisplayBitmapTask,也就是和LoadAndDisplayImageTask.run的最后代码段类似,所以最终显示方法还是一样的。
至此,完成了ImageLoader图片加载到显示的流程逻辑梳理。

你可能感兴趣的:([源码分析]ImageLoader加载图片)