ImageLoader硬盘缓存解析

本文介绍了Universal Image Loader(UIL)的硬盘缓存机制,包括Cache的两大组成部分——BaseDiskCache和DiskLruCache。BaseDiskCache提供基本的缓存功能,如LimitedAgeDiskCache和UnlimitedDiskCache,而DiskLruCache利用LRU算法进行更高效的缓存管理。详细探讨了各个类的构造方法、保存与删除文件的逻辑以及如何根据日期限制清理过期缓存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

我要说的就是鼎鼎大名的Universal Image Loader,UIL是非常强大的一款图片加载框架,它不仅支持本地图片加载也支持网络图片加载还支持Android自身的drawable文件夹,asset文件夹里面的图片文件加载,也支持视频文件的缩略图加载。可以说是一款非常全面而强大的图片加载框架。说到这里你是不是也对这个框架非常好奇呢?

 

首先我们来看看UIL的代码结构。

我们可以看到UIL的代码结构主要分为两大部分,一部分是cache缓存部分,一部分是core主要用于UIL下载,加载和展示图片功能,当然也暴露了许多定制接口供给开发者使用。

 

Cache硬盘缓存

现在就来看看cache的硬盘缓存部分的源码。

 

大致上硬盘缓存部分有两种DiskCache,一种是基于BaseDiskCache的LimitedAgeDiskCache和UnlimitedDiskCache,另一种是基于DiskLruCache的LruDiskCache。基于BaseDiskCache的DiskCache没有使用LRU算法,基于DiskLruCache的有使用到LRU算法,以上的DiskCache都实现了DiskCache接口。

public interface DiskCache {  
    /** 
     * Returns root directory of disk cache 
     * 
     * @return Root directory of disk cache 
     */  
    File getDirectory();  
  
    /** 
     * Returns file of cached image 
     * 
     * @param imageUri Original image URI 
     * @return File of cached image or <b>null</b> if image wasn't cached 
     */  
    File get(String imageUri);  
  
    /** 
     * Saves image stream in disk cache. 
     * Incoming image stream shouldn't be closed in this method. 
     * 
     * @param imageUri    Original image URI 
     * @param imageStream Input stream of image (shouldn't be closed in this method) 
     * @param listener    Listener for saving progress, can be ignored if you don't use 
     *                    {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener 
     *                    progress listener} in ImageLoader calls 
     * @return <b>true</b> - if image was saved successfully; <b>false</b> - if image wasn't saved in disk cache. 
     * @throws java.io.IOException 
     */  
    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;  
  
    /** 
     * Saves image bitmap in disk cache. 
     * 
     * @param imageUri Original image URI 
     * @param bitmap   Image bitmap 
     * @return <b>true</b> - if bitmap was saved successfully; <b>false</b> - if bitmap wasn't saved in disk cache. 
     * @throws IOException 
     */  
    boolean save(String imageUri, Bitmap bitmap) throws IOException;  
  
    /** 
     * Removes image file associated with incoming URI 
     * 
     * @param imageUri Image URI 
     * @return <b>true</b> - if image file is deleted successfully; <b>false</b> - if image file doesn't exist for 
     * incoming URI or image file can't be deleted. 
     */  
    boolean remove(String imageUri);  
  
    /** Closes disk cache, releases resources. */  
    void close();  
  
    /** Clears disk cache. */  
    void clear();  
}  

 

基于BaseDiskCache部分

可以先来看看BaseDiskCache的代码

public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {  
        if (cacheDir == null) {  
            throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);  
        }  
        if (fileNameGenerator == null) {  
            throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);  
        }  
  
        this.cacheDir = cacheDir;  
        this.reserveCacheDir = reserveCacheDir;  
        this.fileNameGenerator = fileNameGenerator;  
    }  


这是BaseDiskCache的构造方法,里面使用了cacheDir,reserveCacheDir,fileNameGenerator三个参数,cacheDir是第一缓存路径,如无意外的话我们就是使用cacheDir作为缓存路径,而reserveCacheDir是备用缓存路径,是在假如第一缓存路径无法使用的情况下,启用备用缓存路径,fileNameGenerator是FileNameGenerator,默认是HashCodeFileNameGenerator,通过文件名的HashCode生成缓存文件名。

@Override  
    public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {  
        File imageFile = getFile(imageUri);  
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);  
        boolean loaded = false;  
        try {  
            OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);  
            try {  
                loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);  
            } finally {  
                IoUtils.closeSilently(os);  
            }  
        } finally {  
            if (loaded && !tmpFile.renameTo(imageFile)) {  
                loaded = false;  
            }  
            if (!loaded) {  
                tmpFile.delete();  
            }  
        }  
        return loaded;  
    }  

 

 

该Save方法实现的是将InputStream通过IO流生成到一个.tmp后缀的缓存文件。当然缓存文件还是和uri相关联。

@Override  
    public boolean remove(String imageUri) {  
        return getFile(imageUri).delete();  
    }  


通过remove方法我们可以看出,通过相关的uri,对其文件删除,本质上是使用了File的接口。

UnlimitedDiskCache基本上就是继承于BaseDiskCache,没有任何改动,所以看看LimitedAgeDiskCache,也是继承于BaseDiskCache,但是有两个特有的成员变量。

 

private final long maxFileAge;  
  
private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());  

一个是maxFileAge,用于记录LimitedAgeDiskCache的日期限制,单位为秒。还有一个是loadingDates,是一个Map用于记录File和Date之间的联系。

 

Save方法里面都用到了rememberUsage(String uri);的方法,事实上就是在Save一个文件的时候,把该文件的修改日期设置上当前时间,并且把文件记录添加到loadingDates里面。

private void rememberUsage(String imageUri) {  
        File file = getFile(imageUri);  
        long currentTime = System.currentTimeMillis();  
        file.setLastModified(currentTime);  
        loadingDates.put(file, currentTime);  
    }  


在通过get(String uri);的方法的时候,对get的File进行检查,如果超过日期限制就删除该文件。

@Override  
    public File get(String imageUri) {  
        File file = super.get(imageUri);  
        if (file != null && file.exists()) {  
            boolean cached;  
            Long loadingDate = loadingDates.get(file);  
            if (loadingDate == null) {  
                cached = false;  
                loadingDate = file.lastModified();  
            } else {  
                cached = true;  
            }  
  
            if (System.currentTimeMillis() - loadingDate > maxFileAge) {  
                file.delete();  
                loadingDates.remove(file);  
            } else if (!cached) {  
                loadingDates.put(file, loadingDate);  
            }  
        }  
        return file;  
    }  

 

 

DiskLruCache简介

DiskLruCache是google的开源代码,但是并不在Android SDK中,下载地址  https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
 
DiskLruCache的作用在于有自己的journal文件来记录缓存存取记录,具体介绍可以看郭大神的 Android DiskLruCache完全解析,硬盘缓存的最佳方案
 
private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount)  
            throws IOException {  
        try {  
            cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);  
        } catch (IOException e) {  
            L.e(e);  
            if (reserveCacheDir != null) {  
                initCache(reserveCacheDir, null, cacheMaxSize, cacheMaxFileCount);  
            }  
            if (cache == null) {  
                throw e; //new RuntimeException("Can't initialize disk cache", e);  
            }  
        }  
    }  

 
我们可以看到initCache函数里面的cache就是调用DiskLruCache的open方法生成的,后两个参数分别指定了缓存的最大值和缓存文件个数的最大值。

 

@Override  
    public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {  
        DiskLruCache.Editor editor = cache.edit(getKey(imageUri));  
        if (editor == null) {  
            return false;  
        }  
  
        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);  
        boolean copied = false;  
        try {  
            copied = IoUtils.copyStream(imageStream, os, listener, bufferSize);  
        } finally {  
            IoUtils.closeSilently(os);  
            if (copied) {  
                editor.commit();  
            } else {  
                editor.abort();  
            }  
        }  
        return copied;  
    }  

 
在这段代码里我们可以看出DiskLruCache使用的是DiskLruCache.Editor对缓存文件进行处理操作。
 
大家可以看到LruDiskCache和前面以BaseDiskCache为基础的DiskCache最大的区别在于,LruDiskCache有设置最大缓存大小,最大缓存文件个数,基于DiskLruCache实现了缓存的LRU算法,即Least Recently Used的缩写,在缓存文件大小或者个数快要超过限制的时候,会删除掉最近最少用的文件,以保证缓存文件不超过限制。有效的节约了内存。
 
可以在DiskLruCache代码里面体现出来。

 

private void trimToSize() throws IOException {  
        while (size > maxSize) {  
            Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();  
            remove(toEvict.getKey());  
        }  
    }  
  
    private void trimToFileCount() throws IOException {  
        while (fileCount > maxFileCount) {  
            Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();  
            remove(toEvict.getKey());  
        }  
    }  

 
以上代码就可以体现到当文件个数超过限制的时候,DiskLruCache会自动的清掉最近最少用的那个文件。至于里面的lruEntries用的就是LinkedHashMap,用这个数据结构很容易实现LRU算法。
 
关于UIL硬盘缓存就先介绍到这里。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值