Universal-Image-Loader的图片加载流程源码分析

概述

Universal-Image-Loader是经典的图片加载框架,虽然现在该项目不再维护,但对于初学者依旧是值得学习的开源项目之一,本文就该框架的加载图片流程做简要梳理,希望读者有所收获。
该文参考了【codeKK】 Android Universal Image Loader 源码分析一文,该文详细分析了Universal-Image-Loader的设计思想,想深入了解,可以祥读此文。

基本工作流程

首先来看看作者给出的工作流程图:


上图基本的加载流程是当图片请求发出时,首先会从内存缓存中查找该bitmap是否存在,如果存在,则由BitmapProcessor这个类来进行处理,然后由DisplayDisplaer这个类来进行显示;
如果内存缓存不存在该图片,那么会从硬盘缓存查找,如果存在,则由ImageDecoder类来将图片解析为Bitmap,然后由BitmapProcessor来进行图片处理,然后由MemoryCache进行内存缓存,方便下次查找,之后再交由BitmapProcessorDisplayDisplaer来进行处理和最终显示;
如果内存缓存和硬盘缓存都没有找到该图片,那么将由ImageDownloader来下载图片,然后将该图片由DiskCache来进行硬盘缓存,缓存好后,之后的流程就和上面的流程一致了。

使用方法

了解了这个基本流程,我们看看Universal-Image-Loader的基本使用方法:

首先使用时候需要初始化设置:
<pre><code>
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);

ImageLoader.getInstance().init(configuration);
</pre></code>
传递的this参数为Context类型,因此一般会在自定义的Application中的onCreate方法中进行初始化:
<pre><code>
public class BaseApplication extends Application{

    private static Context mAppContext;    

    @Override    
    public void onCreate() { 
    super.onCreate(); 
    //用于初始化默认配置,会配置一些默认参数
    ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
    //将上面初始化的配置传递给ImageLoader  
    ImageLoader.getInstance().init(configuration);    
  }

}
</pre></code>

在正式加载图片之前,我们还可能需要做一些配置:

public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.bj_weixianshi)//加载中的等待图片
            .displayer(new SimpleBitmapDisplayer())//选择的显示类
            .showImageOnFail(R.drawable.bj_weixianshi)//显示失败加载的图片
            .cacheInMemory(true)//开启内存缓存
            .cacheOnDisk(true)//开启硬盘缓存
            .bitmapConfig(Bitmap.Config.RGB_565)//图片显示模式
            .build();

好了,这样就可以调用真正的加载方法进行展示了:

ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );

方法很简单,第一个参数就是图片URI,支持类型:

"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)

可以看到支持本地和网络的图片uri;
第二个参数就是ImageView,第三个参数就是上面的simpleOptions,通过这三个参数,就能完成图片的显示。

流程分析

首先我们来这个这行代码:
ImageLoader.getInstance().init(configuration);
看起来是个单例模式,我们跟踪源码看看:

public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }

很典型的单例模式,保证全局只有一个ImageLoader,不必重复创建对象,节省内存。
再来看看配置:

public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.bj_weixianshi)//加载中的等待图片
             ...省略
            .build();

因为可选参数较多,这里用了建造者模式。
好了,看完上面,分析下加载图片的流程:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
该方法最终会调用到这个方法:

public void displayImage(String uri, //图片的uri,可能来自本地或者网络
                             ImageAware imageAware,//该接口类型主要封装了一些获取View控件宽高等的常见方法,实现类之一ImageViewAware
                             DisplayImageOptions options,//展示图片的一些可选项封装类,如是否缓存,同步异步等
                             ImageSize targetSize,//图片显示的最终尺寸,封装处理类,主要是给图片一个合理的显示尺寸
                             ImageLoadingListener listener,//图片加载监听,加载开始,加载失败,加载完成,加载取消几种状态
                             ImageLoadingProgressListener progressListener//图片加载中的监听,可用于显示加载进度条
    ) {
        checkConfiguration();//检查配置是否为null,为Null报异常
        if (imageAware == null) {//检查配置是否为null,为Null报异常
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        if (listener == null) {//检查加载监听,如果为Null,就加载defaultListener
            listener = defaultListener;
        }
        if (options == null) {//加载配置选项,为null,则加载默认的options
            options = configuration.defaultDisplayImageOptions;
        }

        if (TextUtils.isEmpty(uri)) {//检查图片uri是否为空
            engine.cancelDisplayTaskFor(imageAware);//空的情况下,取消加载任务
            listener.onLoadingStarted(uri, imageAware.getWrappedView());//调用监听方法
            if (options.shouldShowImageForEmptyUri()) {//如果配置了空uri情况下的图片显示,那么加载默认图片
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {//否则显示Null
                imageAware.setImageDrawable(null);
            }
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//加载完成,调用监听
            return;
        }

        if (targetSize == null) {//检查图片大小配置,如果为Null,生成默认的图片大小
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//根据uri和targetSize来生成内存缓存的Key
        //将要显示的View和key加入一个线程同步的缓存Map,key为view的id,value为缓存memoryCacheKey,该map用于判断加载任务是否重复
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

        listener.onLoadingStarted(uri, imageAware.getWrappedView());//调用监听方法

        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根据Key从内存缓存查找是否有缓存的bitmap
        if (bmp != null && !bmp.isRecycled()) {//检查缓存的bitmap是否为null,或是否被回收
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

            if (options.shouldPostProcess()) {//是否进行bitmap处理
                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将进行相关处理,并最终显示图片
                    displayTask.run();
                } else {//异步加载,提交displayTask到任务队列,再进行处理显示
                    engine.submit(displayTask);
                }
            } else {//不进行bitmap处理,直接显示图片
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);//加载完成,调用监听
            }
        } else {//如果内存中没有缓存的bitmap,则根据uri从本地缓存或网络加载
            if (options.shouldShowImageOnLoading()) {//如果设置了加载中的图片,则进行显示
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {//如果设置了加载前重置图片,那么给图片设置Null
                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立即执行
                displayTask.run();
            } else {//否则提交displayTask到任务队列再执行
                engine.submit(displayTask);
            }
        }
    }

源码中进行了简单注释,整个加载流程就在这一个方法中。
我们将其中几个重要步骤分解来看:
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根据Key从内存缓存查找是否有缓存的bitmap
从内存缓存中查找缓存的bitmap,我们看看memoryCache的具体实现:
final MemoryCache memoryCache;ImageLoaderConfiguration的成员变量,看看它在哪里初始化的:

        public ImageLoaderConfiguration build() {
            initEmptyFieldsWithDefaultValues();
            return new ImageLoaderConfiguration(this);
        }

        private void initEmptyFieldsWithDefaultValues() {
            ...省略
            if (memoryCache == null) {//如果没有配置,则创建默认的内存缓存
                memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
            }
            ...省略
        }

我们再看看这个方法:

        public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
        if (memoryCacheSize == 0) {
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            int memoryClass = am.getMemoryClass();
            if (hasHoneycomb() && isLargeHeap(context)) {//如果SDK版本为3.0以上,并配置了android:largeHeap="true",可以申请大内存
                memoryClass = getLargeMemoryClass(am);
            }
            memoryCacheSize = 1024 * 1024 * memoryClass / 8;//  1/8可用内存
        }
        return new LruMemoryCache(memoryCacheSize);//默认实现为LruMemoryCache
    }

LruMemoryCache为接口MemoryCache的实现类,作者为我们提供了多种MemoryCache的实现类,来适应不用的内存缓存需求:

MemoryCache的多种实现.png

有兴趣的同学可以分析下这些缓存实现,本文只分析LruMemoryCache,其实该类个人理解是简化版的LruCache,看看源码:

/**
 * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
 * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
 * become eligible for garbage collection.<br />
 * <br />
 * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.8.1
 *
 * 缓存了一定数量的Bitmap的强引用。当一个Bitmap被访问时,它会移动到序列的队尾。当缓存满时,再添加Bitmap,会将
 * 序列头部的Bitmap释放掉,等待GC回收。
 *
 */
public class LruMemoryCache implements MemoryCache {

    //内部使用LinkedHashMap来保存Bitmap
    private final LinkedHashMap<String, Bitmap> map;
    //最大缓存的字节数
    private final int maxSize;
    /** Size of this cache in bytes */
    private int size;//当前缓存的字节数

    /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {//检查设置的缓存大小,小于0抛出异常
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //创建一个基于访问顺序的LinkedHashMap,设置false就是默认的插入顺序,这里不讨论LinkedHashMap的实现了
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }

    /**
     * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
     * of the queue. This returns null if a Bitmap is not cached.
     * 根据key获取缓存的Bitmap,如果能够获取到,那么该引用会移动到队列尾部。如果没有缓存就返回Null
     */
    @Override
    public final Bitmap get(String key) {
        if (key == null) {//检查key是否为null,是null抛异常
            throw new NullPointerException("key == null");
        }
        //同步,多线程访问时保证线程安全
        synchronized (this) {
            return map.get(key);
        }
    }

    /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue.
     *  put进的bitmap会放进队尾
     */

    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {//检查key和value值,如果为null抛出异常
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {//开启同步
            size += sizeOf(key, value);//累计计算bitmap的大小
            Bitmap previous = map.put(key, value);//加入map
            if (previous != null) {//如果之前存在该缓存的bitmap,那么size就不再累计该bitmap的大小
                size -= sizeOf(key, previous);//
            }
        }
        //检查size是否在合理的范围内,如果不再做相应处理
        trimToSize(maxSize);
        return true;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
     *
     * 该方法主要控制缓存的bitmap不超过maxSize,一旦超过就移除最久没用用过的bitmap,直到小于maxSize
     */
    private void trimToSize(int maxSize) {
        while (true) {//开启循环
            String key;
            Bitmap value;
            synchronized (this) {//开启同步
                if (size < 0 || (map.isEmpty() && size != 0)) {//检查size或map是否正常,否则抛出异常
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }
                //如果size小于maxSize或者map是空的,那么说明map状态正常,结束循环
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                //查找队列头部存储的bitmap
                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {//如果要移除的entry对象为null,那么也结束循环
                    break;
                }
                //获取key,和value
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除对应的bitmap
                size -= sizeOf(key, value);//重新计算size大小
            }
        }
    }

    /** Removes the entry for {@code key} if it exists.
     *  根据Key移除对应的bitmap
     */
    @Override
    public final Bitmap remove(String key) {
        if (key == null) {//检查key是否为null,null抛出异常
            throw new NullPointerException("key == null");
        }

        synchronized (this) {//开启线程同步
            Bitmap previous = map.remove(key);//移除
            if (previous != null) {//如果之前的bitmap存在,那么重新计算size大小
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }

    @Override
    public Collection<String> keys() {//获取key的set集合
        synchronized (this) {
            return new HashSet<String>(map.keySet());
        }
    }

    @Override//清空map
    public void clear() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * Returns the size {@code Bitmap} in bytes.
     * <p/>
     * An entry's size must not change while it is in the cache.
     * 计算size方法
     */
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public synchronized final String toString() {
        return String.format("LruCache[maxSize=%d]", maxSize);
    }
}

设计还是比较清晰简单的,主要是通过LinkedHashMap来进行存储。
如果内存缓存中获取不到bitmap,那么将根据uri从本地或网络进行加载,所有的信息都封装在LoadAndDisplayImageTask类中,该类实现了Runnable接口,因此整个过程是在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();//判断当前任务是否正常,view是否被回收,任务是否正常
            //再次从内存中获取缓存的bitmap,个人理解是当请求任务很多时,很可能之前的线程,已经将图片缓存了,所以再次获取
            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);//显示图片
    }

这里看下bmp = configuration.memoryCache.get(memoryCacheKey);,如果if (bmp == null || bmp.isRecycled())那么就会走到bmp = tryLoadBitmap();,我们看看这个方法:

    /**
     * 获取图片
     * @return
     * @throws TaskCancelledException
     */
    private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {//根据uri从本地缓存获取图片的缓存文件
            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
                bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
            }
            //判断是否bitmap是否存在,如果不存在,那么将从网络进行加载
            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
                loadedFrom = LoadedFrom.NETWORK;//做网络标记

                String imageUriForDecoding = uri;
                //如果设置了本地缓存开启,tryCacheImageOnDisk()方法会去网络获取图片,并缓存到本地
                if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                    imageFile = configuration.diskCache.get(uri);
                    if (imageFile != null) {
                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                    }
                }

                checkTaskNotActual();//检测任务是否正常
                //如果设置了本地缓存开启,那么bitmap会从本地加载,如果没有则从网络加载
                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;
    }

通过上面的方法分析我们可以大致知道如果开启了本地缓存,那么将会执行tryCacheImageOnDisk()先下载图片再本地缓存,而如果没用开启缓存,那么会执行decodeImage(imageUriForDecoding)去网络下载图片,这里我们分析下通过本地缓存和网络加载两种方式,主要了解下两者的实现区别,看看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);
                   // 如果设置了本地图片缓存的最大宽高,默认为0,重新设置图片大小并再次本地缓存
                    resizeAndSaveImage(width, height); 
                }
            }
        } catch (IOException e) {
            L.e(e);
            loaded = false;
        }
        return loaded;
    }

看看downloadImage()方法:

private boolean downloadImage() throws IOException {
        //根据uri来获取流
        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);
            }
        }
    }

getStream()方法默认由BaseImageDownloader对象实现:

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://content provider
                return getStreamFromContent(imageUri, extra);
            case ASSETS://assets 目录
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE://图片
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

根据uri的前缀来选择不同的加载方式,这里我们看看从getStreamFromNetwork(imageUri, extra)`方法:

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);//创建一个HttpURLConnection对象
        int redirectCount = 0;//重定向次数
        //最大5次重定向
        while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }

        InputStream imageStream;
        try {//获取图片流
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }
        if (!shouldBeProcessed(conn)) {//如果状态码不是200,那么关闭流
            IoUtils.closeSilently(imageStream);
            throw new IOException("Image request failed with response code " + conn.getResponseCode());
        }
        //重新封装流
        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }

至此,就从网络获取到图片流了,然后转换为bitmap,然后进行本地缓存,如果设置了diskCacheSize或者diskCacheFileCount,只要满足任意一个条件,那么就会用LruDiskCache实现,否者用UnlimitedDiskCache实现,先看看UnlimitedDiskCache吧,其实该类UnlimitedDiskCache,就是BaseDiskCache,这个对本地缓存没有大小限制,所以看看BaseDiskCache的实现:

//根据图片uri,来保存uri
    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        File imageFile = getFile(imageUri);//获取缓存文件
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);//临时文件
        OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
        boolean savedSuccessfully = false;
        try {//将bitmap保存到临时文件
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        } finally {
            IoUtils.closeSilently(os);
            //如果保存成功,但重命名没有成功则保存失败
            if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
                savedSuccessfully = false;
            }
            //如果没有保存成功,则删除临时文件
            if (!savedSuccessfully) {
                tmpFile.delete();
            }
        }
        bitmap.recycle();
        return savedSuccessfully;
    }


/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
    //根据uri获取缓存图片文件
    protected File getFile(String imageUri) {
        String fileName = fileNameGenerator.generate(imageUri);//生成文件名
        File dir = cacheDir;//缓存目录
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {//如果缓存目录不存在,那么就用备用缓存目录
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

主要就是这两个方法了,还是比较简单。
至于LruDiskCache类的源码,内部其实是由JakeWhartonDiskLruCache实现的,所以单独另开一篇分析此类,本文分析到此为止。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容