ImageLoader源码分析

1.介绍

ImageLoader是Android平台必备的图片加载、缓存、显示库,配置选项丰富。下面列表内容是我从小米note文件夹复制的ImageLoader磁盘缓存文件夹列表,足以说明ImageLoader用户量之大。

com.miui.systemAdSolution

com.android.email

com.xiaomi.mitunes

com.baidu.BaiduMap

com.miui.mipub

com.miui.cleanmaster

com.miui.analytics

com.android.fileexplorer

com.xiaomi.xmsf

com.miui.cloudbackup

com.android.browser

com.xiaomi.pass

com.xiaomi.account

com.miui.cloudservice

com.mipay.wallet

com.miui.voip

com.android.phone

com.android.thememanager

com.miui.fmradio

com.miui.miuibbs

com.miui.virtualsim

com.xiaomi.gamecenter

com.xiaomi.vip

com.miui.gallery

com.xiaomi.market

com.ushaqi.zhuishushenqi

com.UCMobile

com.eg.android.AlipayGphone

com.netease.cloudmusic

.log

com.taobao.taobao

com.baidu.lbs.waimai

tv.danmaku.bili

com.miui.video

com.sina.weibo

com.yipiao

com.xiaomi.payment

com.greenpoint.android.mc10086.activity

com.gypsii.weibo

com.mi.vtalk

com.amap.android.location

com.tencent.mobileqq

com.qzone

com.avalon.cave

com.miui.securitycenter

com.gameabc.zhanqiAndroid

com.maxthon.mge

com.smk

me.ele

com.sankuai.meituan.takeoutnew

com.qihoo.gameunion.s

com.xiaomi.shop

com.example.mycamera

com.taobao.mobile.dipei

com.android.calendar

com.android.providers.downloads

com.teambition.teambition

com.taou.maimai

system

perftest

com.android.soundrecorder

com.mfashiongallery.emag

com.jingdong.app.mall

com.unionpay

leancloud

cn.wps.moffice_eng

com.taobao.movie.android

com.xiaomi.gamecenter.sdk.service

com.autonavi.minimap

com.xiaomi.router

com.weibo.app.movie

com.alibaba.android.rimet

com.sohu.inputmethod.sogou.xiaomi

com.xiaomi.scanner

com.miui.securitycore

com.baidu.tieba

air.tv.douyu.android

com.detu.main

com.google.android.gms

com.google.android.apps.maps

com.qihoo.appstore

com.nostra13.universalimageloader

作为一款这么常用的开发依赖库,分析其加载、缓存、显示流程还是很有必要的,所以就开始吧!

2. 源码分析

  • displayImage内部流程

在日常开发中,总是以ImageLoader的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);
            }
        }
    }

其中4-12行:对一些控制参数赋予默认值

其中14-24行: 资源文件地址为空,流程结束

其中26-28行:通过defineTargetSizeForView方法对targetSize赋值,这里用到了

传入的maxImageWidthForMemoryCache和maxImageHeightForMemoryCache;这里的targetSize会对图片的内存缓存大小产生影响。当界面比较卡顿的时候,可以通过memoryCacheExtraOptions配置方法,增加内存缓存的图片数量来增加界面流畅度。

其中29行:创建一个uri_width x height形式的key,作为内存缓存的标识

其中34行:首先通过key从内存缓存中查找,如果能找到并且bitmap没有被回收,执行38-51行逻辑。

其中38-51行:如果shouldPostProcess,这个是在

用于对已经缓存在内存中的图片进行修改。

其中53-68行:内存缓存查找不到, 执行LoadAndDisplayImageTask 任务,加载、缓存并显示图片。这里也可以通过

来选择是同步加载还是异步加载

  • LoadAndDisplayImageTask 类的run方法
    public void run() {
        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();

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

其中2行:判断任务是否需要暂停,这里为配合ImageLoaderEngine类的

pause、resume操作。同时对view引用是否存在进行校验,View假如都已经被回收掉了,后续的操作也就没有意义了。

其中3行:判断是否需要在任务前延迟数秒,为了配合

其中14行:调用checkTaskNotActual()检查View是否被释放,后续操作该方法也被多次调用。

其中16行:再次尝试从内存缓存查找,这里又一次从内存缓存查找是因为图片加载往往是异步的,可能刚才在displayImage方法中的时候还没有被缓存到内存。

其中18-19行:没有内存缓存或者bitmap已经被回收,就要调用tryLoadBitmap从磁盘缓存或者网络缓存。

其中24-28行:配合preProcessor配置

对从磁盘或者网路加载的bitmap进行操作,这里对bitmap的操作发生在内存缓存前,所以也可以影响到内存缓存图片的大小和数量,但是对bitmap的操作需要一定的时间,所以也会对界面造成前期卡顿,后期流畅的影响。

其中32-35行:根据cacheInMemory配置

来决定是否内存缓存

其中42-47行:和displayImage方法中一样,对内存缓存后的bitmap进行操作用于显示。

其中57-58行:添加显示任务

  • tryLoadBitmap方法流程
private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {
            File imageFile = configuration.diskCache.get(uri);
            if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
                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;
                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;
    }

其中4行:尝试从磁盘缓存查找

其中5-11行:从磁盘查找到并且文件可用,利用decodeImage方法加载

文件流,内部流程后面会分析。

其中12-30行:磁盘加载不到或者加载的bitmap不可用,17行配合cacheOnDisc配置

调用tryCacheImageOnDisk从网络(这里网络指的是uri,包含file本地文件)加载并进行磁盘缓存操作。

其中25行:同样进行decodeImage操作。

  • tryCacheImageOnDisk方法流程
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

        boolean loaded;
        try {
            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;
    }

其中6行:调用downloadImage从网络加载数据

其中7-16行:根据ImageLoaderConfiguration中的diskCacheExtraOptions配置

调用resizeAndSaveImage方法对本地缓存文件进行宽高控制

  • downloadImage方法流程
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);
            }
        }
    }

其中2行:会根据uri规则(file、http、https、assets等)加载文件原始流。

其中8行:将上面原始流缓存到文件流中,这里一般不对文件宽高进行更改

  • resizeAndSaveImage方法流程
private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
        // Decode image file, compress and re-save it
        boolean saved = false;
        File targetFile = configuration.diskCache.get(uri);
        if (targetFile != null && targetFile.exists()) {
            ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
            DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
                    .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
            ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
                    Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
                    getDownloader(), specialOptions);
            Bitmap bmp = decoder.decode(decodingInfo);
            if (bmp != null && configuration.processorForDiskCache != null) {
                L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
                bmp = configuration.processorForDiskCache.process(bmp);
                if (bmp == null) {
                    L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
                }
            }
            if (bmp != null) {
                saved = configuration.diskCache.save(uri, bmp);
                bmp.recycle();
            }
        }
        return saved;
    }

该方法的主要作用根据ViewScaleType和配置的磁盘属性对磁盘缓存文件

宽高控制和比例缩放。其中主要方法是12行,默认的decode是BaseImageDecoder,开发者也可以通过配置传入自己的decoder

下面来分析默认的BaseImageDecoder的decode流程

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

其中11行:会根据文件exif属性信息和considerExifParams配置

来对磁盘缓存返回rotate旋转角度

其中13行:会根据imageScaleType配置和View的scaleType计算出一个

scale比例供14行对磁盘缓存图片文件进行缩放

其中22-23行:根据前面计算的rotate旋转角度对图片进行旋转操作

3. 总结

通过上述流程分析,ImageLoader的加载、缓存、显示的流程就梳理出来了。也可以看出缓存是按照内存缓存、磁盘缓存、网络加载的流程进行的。Over!

时间: 2024-10-10 17:34:24

ImageLoader源码分析的相关文章

Volley源码分析(2)----ImageLoader

一:imageLoader 先来看看如何使用imageloader: public void showImg(View view){ ImageView imageView = (ImageView)this.findViewById(R.id.image_view); RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext()); ImageLoader imageLoader = new ImageLoader(m

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur

Android图片加载库Picasso源码分析

图片加载在Android开发中是非常重要,好的图片加载库也比比皆是.ImageLoader.Picasso.Glide.Fresco均是优秀的图片加载库. 以上提到的几种图片加载库各有特色.用法与比较,网上已经很多了. 出于学习的角度,个人认为从Picasso入手较好.代码量小,同时API优美,很适合我们学习. 今天笔者就Picasso的源码进行分析,抛出一些图片加载的技术细节供园友参考. PS:建议园友先大致看一下源码. 我们对图片加载的要求 1.加载速度要快 2.资源消耗要低 3.加载图片不

Retrofit源码分析以及MVP框架封装使用

阅读此文前请先阅读Retrofit+okhttp网络框架介绍 从上文中我们已经了解通过如下代码即可得到返回给我们call 以及 response对象,今天我们通过源码来分析这个过程是如何实现的. /** * 获取天气数据 * @param cityname * @param key * @return */ @GET("/weather/index") Call<WeatherData> getWeatherData(@Query("format") S

Android网络框架源码分析一---Volley

转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium=mobile_author_hots&utm_source=recommendation 公司最近新起了一个项目,对喜欢尝鲜的我们来说,好处就是我们可以在真实的项目中想尝试一些新技术,验证想法.新项目对网络框架的选取,我们存在三种方案: 1.和我们之前的项目一样,使用Loader + HttpCli

Volley源码分析二

在前两天我发布的文章:Volley源码分析一 中我较为详细的分析了Volley,今天继续,这篇文章会讲一些上一篇没有提到的比较细节的点,以及对于Volley源码中一些可以优化的实现的一些思考 ByteArrayPool的分析 byte[] 的回收池,用于 byte[] 的回收再利用,减少了内存的分配和回收.主要通过一个元素长度从小到大排序的ArrayList作为 byte[] 的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素. public synchronized

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu