码迷,mamicode.com
首页 > 其他好文 > 详细

ImageLoader源码分析

时间:2016-07-14 15:40:28      阅读:515      评论:0      收藏:0      [点我收藏+]

标签:

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!

ImageLoader源码分析

标签:

原文地址:http://blog.csdn.net/wchicho/article/details/51899977

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!