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的基本使用方法:

首先使用时候需要初始化设置:

 
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);

ImageLoader.getInstance().init(configuration);
传递的`this`参数为`Context`类型,因此一般会在自定义的`Application`中的`onCreate`方法中进行初始化:
 
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);    
  }

}

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

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实现的,所以单独另开一篇分析此类,本文分析到此为止。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值