Android知识总结:Universal-Imageloader学习笔记5 一种特殊情况下的图片缓存方式

背景

在最近的项目中,由于后台的特殊要求,每次加载图片时,图片url都会携带一个时间戳,即如下图片格式为这样的形式:http://xxxx/aaaa/cccc.jpg?timestamp 其中问号前面的部分是不变的,而timestamp每次是不同的。换句话说,每次加载图片的url是不同的。也就是说,如果不经过特殊处理,ImageLoader的缓存是没有办法使用的,也就是说所有图片只要显示必须当下下载,也就别提什么用户体验了。
通过抓包了解到,服务端每张图片的url其实是在url的基础上加了一个时间戳,也就是说对于同一张图片,每次下载时其实是在一个固定url的基础上加时间戳构成真的url。换句话说,就是上面那个示例url其实问号前面的部分是不变的,既然有不变的地方,可否为我们所用?
感觉上当然是可以的,实现就需要我们重新分析一下ImageLoader是怎样加载缓存图片的。

加载图片过程

详细过程分析可以参考如下链接http://blog.csdn.net/lidec/article/details/50133533,此处我们只看bitmap是如何被存入与取出缓存的。
由之前的博文可以知道,下载的主要业务逻辑存在于ImageLoader的displayImage方法。
图片的缓存分为内存缓存(memoryCache)和硬盘缓存(diskCache),如果我们能找到url与缓存索引的对应关系,我们就可以直接将url中固定不变的部分拿出来,用这部分生成索引,然后当我们查找缓存中的索引时,直接使用固定不变那部分url生成索引。总之,就是用url中不带时间戳的部分来作为取缓存的依据,这样,不管后面时间戳怎么变化,我们都能得到唯一的图片。
通过分析源码可知,图片内存缓存的索引生成方式如下:
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
......
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        通过图片的uri与大小,经过一定的算法生成内存缓存索引,然后通过这个索引就可以在缓存中获取bitmap
当内存中不存在缓存时,我们就需要从硬盘中获取。在前文分析配置文件时候,我们发现ImageLoader中为我们提供了一个FileNameGenerator接口,通过重写这个接口可以实现我们对硬盘缓存文件的自定义命名。所以,我们可以通过实现自己的FileNameGenerator,来截取我们所需要的有效url,并使用一定的算法生成我们的命名。是不是这样呢,我们看看源码。
 //尝试在文件中加载  
        File imageFile = configuration.diskCache.get(uri);  
        if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {  
            ......  
            //从文件中解码图片  
            bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));  
        }  
我们可以看看diskCache的get(uri)是怎么做的。
    protected File getFile(String imageUri) {
        String fileName = this.fileNameGenerator.generate(imageUri);
        File dir = this.cacheDir;
        if(!this.cacheDir.exists() && !this.cacheDir.mkdirs() && this.reserveCacheDir != null && (this.reserveCacheDir.exists() || this.reserveCacheDir.mkdirs())) {
            dir = this.reserveCacheDir;
        }

        return new File(dir, fileName);
    }
可见,里面是调用fileNameGenerator来生成的文件名,而解码文件时,用到的file就是这个文件民对应的file,所以,我们只需要实现自己的fileNameGenerator,将时间戳部分截取掉,用其余部分生成文件名,就可以实现uri的固定。

两个实际问题

原理就是这样,而让人蛋疼的问题还在后面,首先,要修正以前博客配置ImageLoder文章的一个bug,经过试验,在config中设置fileNameGenrator是无效的。如下
config.diskCacheFileNameGenerator(new MyHashCodeNameGenerator());
这么写就扯了dan了,其实打开缓存文件夹,里面根本不是按照你的规则生成的文件名,而是默认的hashcode生成。通过阅读源码,我发现必须在设置diskCache类型的时候传入将Generator构造函数,这样才能有效设置。代码如下:
ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(context);
         ......
        config.diskCache(new BaseDiskCache(new File(FileUtils.getImageDir()), null, new MyHashCodeNameGenerator()) {
        });
        //config.diskCacheFileNameGenerator(new MyHashCodeNameGenerator());    这个没用
        ImageLoader.getInstance().init(config.build());
第二坑是我一开始直接截取字符串后,用这个字符串的hashcode作为文件名返回,后来发现对于相同的字符串,返回的hashcode是不一样的。所以,是时候回头看看java基础了............. 然后我开始参照原文件中的MD5NameGenerator进行了实现,MD5加密只和字符有关系,只要传入相同字符串,返回值必定相同,所以只是对问号前的部分进行MD5编码并返回字符串。代码如下
/**
 * Created by vonchenchen on 2016/1/6 0006.
 */
public class MyHashCodeNameGenerator implements FileNameGenerator {

    private static final String HASH_ALGORITHM = "MD5";
    private static final int RADIX = 10 + 26; // 10 digits + 26 letters

    @Override
    public String generate(String s) {

        String urlHeader = s.split("[?]")[0];
        byte[] md5 = getMD5(urlHeader.getBytes());
        BigInteger bi = new BigInteger(md5).abs();
        return bi.toString(RADIX);

        //String urlHeader = s.split("[?]")[0];    //这样写虽然截取后url一样,但是生成的hashcode每次不同
        //return String.valueOf(s.hashCode());
    }

    private byte[] getMD5(byte[] data) {
        byte[] hash = null;
        try {
            MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
            digest.update(data);
            hash = digest.digest();
        } catch (NoSuchAlgorithmException e) {
            //L.e(e);
        }
        return hash;
    }
}

这样,我们就在不改变ImageLoader源码的情况下,为程序增加了硬盘缓存,节省了流量,也使图片加载更为流畅。问题有点烦人,原理其实很简单,实现也走了些弯路,总结经验,稳步前进。
 
  
 
 

你可能感兴趣的:(android,安卓,图片加载,ImageLoader,三级缓存)