Universal-Image-Loader源码分析,及常用的缓存策略
讲到图片请求,主要涉及到网络请求,内存缓存,硬盘缓存等原理和4大引用的问题,概括起来主要有以下几个内容:
原理示意图
主体有三个,分别是UI,缓存模块和数据源(网络)。它们之间的关系如下:
① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。
④ 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。
⑤ 下载图片:启动异步线程,从数据源下载数据(Web)。
⑥ 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中。
UIL中的内存缓存策略
1. 只使用的是强引用缓存
LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用,下面我会从源码上面分析这个类)
2.使用强引用和弱引用相结合的缓存有
UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用) FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap) LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)
3.只使用弱引用缓存
WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)
我们直接选择UIL中的默认配置缓存策略进行分析。
1.
ImageLoaderConfiguration
config = ImageLoaderConfiguration.createDefault(context);
ImageLoaderConfiguration.createDefault(…)这个方法最后是调用Builder.build()方法创建默认的配置参数的。默认的内存缓存实现是LruMemoryCache,磁盘缓存是UnlimitedDiscCache。
所以android在以后的版本中建议使用LruMemoryCache进行管理。
LruMemoryCache:一种使用强引用来保存有数量限制的Bitmap的cache(在空间有限的情况,保留最近使用过的Bitmap)。每次Bitmap被访问时,它就被移动到一个队列的头部。当Bitmap被添加到一个空间已满的cache时,在队列末尾的Bitmap会被挤出去并变成适合被GC回收的状态。
注意:这个cache只使用强引用来保存Bitmap。
LruMemoryCache实现MemoryCache,而MemoryCache继承自MemoryCacheAware。
1.
public
interface
MemoryCache
extends
MemoryCacheAware<String,
Bitmap>
下面给出继承关系图
通过跟踪LruMemoryCache.get()的代码我们发现,LruMemoryCache声称保留在空间有限的情况下保留最近使用过的Bitmap,这是一个LinkedHashMap<String,
Bitmap>,Icache的源码缓存的代码如下:
@Override
public ICache<String, Bitmap> getBitmapCache() {
if (bitmapCache == null) {
bitmapCache = new ICache<String, Bitmap>() {
@Override
public void remove(String key) {
ImageLoader.getInstance().getMemoryCache().remove(key);
}
@Override
public boolean put(String key, Bitmap value) {
if (key == null || value == null) return false;
return ImageLoader.getInstance().getMemoryCache().put(key, value);
}
@Override
public Collection<String> keys() {
return null;
}
@Override
public Bitmap get(String key) {
return ImageLoader.getInstance().getMemoryCache().get(key);
}
@Override
public void clear() {
ImageLoader.getInstance().getMemoryCache().clear();
}
};
}
return bitmapCache;
}
看到这里我们就清楚LruMemoryCache使用LinkedHashMap来缓存数据,在LinkedHashMap.get()方法执行后,LinkedHashMap中entry的顺序会得到调整。那么我们怎么保证最近使用的项不会被剔除呢?接下去,让我们看看LruMemoryCache.put(...)。
sizeOf(key, value),这个size是什么呢?我们注意到在第19行有一个trimToSize(maxSize),trimToSize(...)这个函数就是用来限定LruMemoryCache的大小不要超过用户限定的大小,cache的大小由用户在LruMemoryCache刚开始初始化的时候限定。
public final boolean put(String key, Bitmap value) { if(key != null && value != null) { synchronized(this) { this.size += this.sizeOf(key, value); Bitmap previous = (Bitmap)this.map.put(key, value); if(previous != null) { this.size -= this.sizeOf(key, previous); } } this.trimToSize(this.maxSize); return true; } else { throw new NullPointerException("key == null || value == null"); } }
所以不难理解我们在用Universal-Image-Loader做图片加载的时候,有时候图片缓存超过阈值的时候,会去重新重服务器加载了
private void trimToSize(int maxSize) { while(true) { synchronized(this) { ) { throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if(this.size > maxSize && !this.map.isEmpty()) { Entry toEvict = (Entry)this.map.entrySet().iterator().next(); if(toEvict != null) { String key = (String)toEvict.getKey(); Bitmap value = (Bitmap)toEvict.getValue(); this.map.remove(key); this.size -= this.sizeOf(key, value); continue; } } return; } } }
这时候我们会有一个以为,为什么遍历一下就可以将使用最少的bitmap缓存给剔除,不会误删到最近使用的bitmap缓存吗?首先,我们要清楚,LruMemoryCache定义的最近使用是指最近用get或put方式操作到的bitmap缓存。其次,之前我们直到LruMemoryCache的get操作其实是通过其内部字段LinkedHashMap.get(...)实现的,当LinkedHashMap的accessOrder==true时,每一次get或put操作都会将所操作项(图中第3项)移动到链表的尾部(见下图,链表头被认为是最少使用的,链表尾被认为是最常使用的。),每一次操作到的项我们都认为它是最近使用过的,当内存不够的时候被剔除的优先级最低。需要注意的是一开始的LinkedHashMap链表是按插入的顺序构成的,也就是第一个插入的项就在链表头,最后一个插入的就在链表尾。假设只要剔除图中的1,2项就能让LruMemoryCache小于原先限定的大小,那么我们只要从链表头遍历下去(从1→最后一项)那么就可以剔除使用最少的项了。
至此,我们就知道了LruMemoryCache缓存的整个原理,包括他怎么put、get、剔除一个元素的的策略。接下去,我们要开始分析默认的磁盘缓存策略了。
UIL中的磁盘缓存策略
幸好UIL提供了几种常见的磁盘缓存策略,你也可以自己去扩展,可以根据他提供的几种缓存策略做进一步的缓存值的限制,
FileCountLimitedDiscCache(可以设定缓存图片的个数,当超过设定值,删除掉最先加入到硬盘的文件) LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件) TotalSizeLimitedDiscCache(设定缓存bitmap的最大值,当超过这个值,删除最先加入到硬盘的文件)
UnlimitedDiscCache(这个缓存类没有任何的限制)
- UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
- LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)
- FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)
- LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
- LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)
在UIL中有着比较完整的存储策略,根据预先指定的空间大小,使用频率(生命周期),文件个数的约束条件,都有着对应的实现策略。最基础的接口DiscCacheAware和抽象类BaseDiscCache
接下来我们看一些硬盘缓存的一些策略:
接下来就给大家分析分析硬盘缓存的策略,这个框架也提供了几种常见的缓存策略,当然如果你觉得都不符合你的要求,你也可以自己去扩展
- FileCountLimitedDiscCache(可以设定缓存图片的个数,当超过设定值,删除掉最先加入到硬盘的文件)
- LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)
- TotalSizeLimitedDiscCache(设定缓存bitmap的最大值,当超过这个值,删除最先加入到硬盘的文件)
- UnlimitedDiscCache(这个缓存类没有任何的限制)
下面我们就来分析分析TotalSizeLimitedDiscCache的源码实现
- /*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
- package com.nostra13.universalimageloader.cache.disc.impl;
- import com.nostra13.universalimageloader.cache.disc.LimitedDiscCache;
- import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
- import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
- import com.nostra13.universalimageloader.utils.L;
- import java.io.File;
- /**
- * Disc cache limited by total cache size. If cache size exceeds specified limit then file with the most oldest last
- * usage date will be deleted.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see LimitedDiscCache
- * @since 1.0.0
- */
- public class TotalSizeLimitedDiscCache extends LimitedDiscCache {
- private static final int MIN_NORMAL_CACHE_SIZE_IN_MB = 2;
- private static final int MIN_NORMAL_CACHE_SIZE = MIN_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024;
- /**
- * @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's
- * needed for right cache limit work.
- * @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the
- * most oldest last usage date will be deleted.
- */
- public TotalSizeLimitedDiscCache(File cacheDir, int maxCacheSize) {
- this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxCacheSize);
- }
- /**
- * @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's
- * needed for right cache limit work.
- * @param fileNameGenerator Name generator for cached files
- * @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the
- * most oldest last usage date will be deleted.
- */
- public TotalSizeLimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int maxCacheSize) {
- super(cacheDir, fileNameGenerator, maxCacheSize);
- if (maxCacheSize < MIN_NORMAL_CACHE_SIZE) {
- L.w("You set too small disc cache size (less than %1$d Mb)", MIN_NORMAL_CACHE_SIZE_IN_MB);
- }
- }
- @Override
- protected int getSize(File file) {
- return (int) file.length();
- }
- }
这个类是继承LimitedDiscCache,除了两个构造函数之外,还重写了getSize()方法,返回文件的大小,接下来我们就来看看LimitedDiscCache
- /*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
- package com.nostra13.universalimageloader.cache.disc;
- import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
- import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
- import java.io.File;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Set;
- import java.util.concurrent.atomic.AtomicInteger;
- /**
- * Abstract disc cache limited by some parameter. If cache exceeds specified limit then file with the most oldest last
- * usage date will be deleted.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see BaseDiscCache
- * @see FileNameGenerator
- * @since 1.0.0
- */
- public abstract class LimitedDiscCache extends BaseDiscCache {
- private static final int INVALID_SIZE = -1;
- //记录缓存文件的大小
- private final AtomicInteger cacheSize;
- //缓存文件的最大值
- private final int sizeLimit;
- private final Map<File, Long> lastUsageDates = Collections.synchronizedMap(new HashMap<File, Long>());
- /**
- * @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's
- * needed for right cache limit work.
- * @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date
- * will be deleted.
- */
- public LimitedDiscCache(File cacheDir, int sizeLimit) {
- this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), sizeLimit);
- }
- /**
- * @param cacheDir Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's
- * needed for right cache limit work.
- * @param fileNameGenerator Name generator for cached files
- * @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date
- * will be deleted.
- */
- public LimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int sizeLimit) {
- super(cacheDir, fileNameGenerator);
- this.sizeLimit = sizeLimit;
- cacheSize = new AtomicInteger();
- calculateCacheSizeAndFillUsageMap();
- }
- /**
- * 另开线程计算cacheDir里面文件的大小,并将文件和最后修改的毫秒数加入到Map中
- */
- private void calculateCacheSizeAndFillUsageMap() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- int size = 0;
- File[] cachedFiles = cacheDir.listFiles();
- if (cachedFiles != null) { // rarely but it can happen, don't know why
- for (File cachedFile : cachedFiles) {
- //getSize()是一个抽象方法,子类自行实现getSize()的逻辑
- size += getSize(cachedFile);
- //将文件的最后修改时间加入到map中
- lastUsageDates.put(cachedFile, cachedFile.lastModified());
- }
- cacheSize.set(size);
- }
- }
- }).start();
- }
- /**
- * 将文件添加到Map中,并计算缓存文件的大小是否超过了我们设置的最大缓存数
- * 超过了就删除最先加入的那个文件
- */
- @Override
- public void put(String key, File file) {
- //要加入文件的大小
- int valueSize = getSize(file);
- //获取当前缓存文件大小总数
- int curCacheSize = cacheSize.get();
- //判断是否超过设定的最大缓存值
- while (curCacheSize + valueSize > sizeLimit) {
- int freedSize = removeNext();
- if (freedSize == INVALID_SIZE) break; // cache is empty (have nothing to delete)
- curCacheSize = cacheSize.addAndGet(-freedSize);
- }
- cacheSize.addAndGet(valueSize);
- Long currentTime = System.currentTimeMillis();
- file.setLastModified(currentTime);
- lastUsageDates.put(file, currentTime);
- }
- /**
- * 根据key生成文件
- */
- @Override
- public File get(String key) {
- File file = super.get(key);
- Long currentTime = System.currentTimeMillis();
- file.setLastModified(currentTime);
- lastUsageDates.put(file, currentTime);
- return file;
- }
- /**
- * 硬盘缓存的清理
- */
- @Override
- public void clear() {
- lastUsageDates.clear();
- cacheSize.set(0);
- super.clear();
- }
- /**
- * 获取最早加入的缓存文件,并将其删除
- */
- private int removeNext() {
- if (lastUsageDates.isEmpty()) {
- return INVALID_SIZE;
- }
- Long oldestUsage = null;
- File mostLongUsedFile = null;
- Set<Entry<File, Long>> entries = lastUsageDates.entrySet();
- synchronized (lastUsageDates) {
- for (Entry<File, Long> entry : entries) {
- if (mostLongUsedFile == null) {
- mostLongUsedFile = entry.getKey();
- oldestUsage = entry.getValue();
- } else {
- Long lastValueUsage = entry.getValue();
- if (lastValueUsage < oldestUsage) {
- oldestUsage = lastValueUsage;
- mostLongUsedFile = entry.getKey();
- }
- }
- }
- }
- int fileSize = 0;
- if (mostLongUsedFile != null) {
- if (mostLongUsedFile.exists()) {
- fileSize = getSize(mostLongUsedFile);
- if (mostLongUsedFile.delete()) {
- lastUsageDates.remove(mostLongUsedFile);
- }
- } else {
- lastUsageDates.remove(mostLongUsedFile);
- }
- }
- return fileSize;
- }
- /**
- * 抽象方法,获取文件大小
- * @param file
- * @return
- */
- protected abstract int getSize(File file);
- }
最后看一些我们需要怎么使用
配置:
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(YmatouApplication.instance()) .threadPriority(Thread.) .memoryCache(new LruMemoryCache(getMemoryCacheSize())) .diskCacheSize(50 * ByteConstants.MB) .diskCacheFileNameGenerator(new Md5FileNameGenerator()) .diskCacheFileCount() .memoryCacheExtraOptions(, ) .threadPoolSize() .memoryCache(new WeakMemoryCache()) .tasksProcessingOrder(QueueProcessingType.FIFO) .defaultDisplayImageOptions(getSimpleOptions().build()) .build(); ImageLoader.getInstance().init(config);
在做网络请求的时候,我们也做些配置,如网络请求之前默认什么背景,请求完成后,请求失败后怎么显示我们都可以在
defaultDisplayImageOptions(DisplayImageOptions.Builder builed)进行配置
private static DisplayImageOptions.Builder getSimpleOptions() { DisplayImageOptions.Builder options = new DisplayImageOptions.Builder() .cacheInMemory(true) .cacheOnDisk(true) .considerExifParams(true) .imageScaleType(ImageScaleType.EXACTLY) .showImageOnLoading(R.drawable.image_loading) .showImageForEmptyUri(R.drawable.image_default) .showImageOnFail(R.drawable.image_failed) .resetViewBeforeLoading(true) .bitmapConfig(Bitmap.Config.ARGB_8888); return options; }
加载图片:
public static void imageloader(String url, ImageView imageView) { displayImage(url, imageView, null); }
Universal-Image-Loader源码分析,及常用的缓存策略的更多相关文章
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
- mybatis源码分析之06二级缓存
上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...
- mybatis源码分析之05一级缓存
首先需要明白,mybatis的一级缓存就是指SqlSession缓存,Map缓存! 通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlS ...
- RecyclerView 源码分析(二) —— 缓存机制
在前一篇文章 RecyclerView 源码分析(一) -- 绘制流程解析 介绍了 RecyclerView 的绘制流程,RecyclerView 通过将绘制流程从 View 中抽取出来,放到 Lay ...
- mybatis缓存源码分析之浅谈缓存设计
本文是关于mybatis缓存模块设计的读后感,关于缓存的思考,关于mybatis的缓存源码详细分析在另一篇文章:https://www.cnblogs.com/gmt-hao/p/12448896.h ...
- Mybatis源码分析之Cache一级缓存原理(四)
之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...
- Spring源码分析(十三)缓存中获取单例bean
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ...
- phpcms 源码分析五:文件缓存实现
这次是逆雪寒的文件缓存实现代码分析: /* [/php] PHPCMS的文本缓存实现: [php] <?php /* 这个文件里面全是有关生成文本缓存的函数.文本缓存是个好东西.一般的项目,我们 ...
- mybatis源码分析(7)-----缓存Cache(一级缓存,二级缓存)
写在前面 MyBatis 提供查询缓存,用于减轻数据库压力,提高数据库性能. MyBatis缓存分为一级缓存和二级缓存. 通过对于Executor 的设计.也可以发现MyBatis的缓存机制(采用模 ...
- Guava cacha 机制及源码分析
1.ehcahce 什么时候用比较好:2.问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...
随机推荐
- XMPP(三)-安卓即时通讯客户端
由于时间原因,所以更新比较慢 ,还请大家谅解,此次是对上篇文章中的安卓客户端初级版本进行的一次更新优化,在这次更新后,就有那么一点样子了,可以拿的出手了,呵呵,还在关注的同学也可以及时下载更新.此次主 ...
- Android 增量更新和升级
在年初的时候,尝试了一把热修复技术,当时选择的是阿里的andfix,使用起来也很简单,这里就不在多少,如果你对andfix有兴趣请链接:点击打开链接.虽然网上将热修复的文章很多,不过我还是想说原理,然 ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- C语言一个双向链表的实现
首先编写头文件,头文件里做相关的定义和声明,DList.h内容如下: #ifndef DList_H #define DList_H typedef int Item; typedef struct ...
- [C]simple code of count input lines,words,chars
This is a simple C program which can count input lines, words and chars. But the number of words are ...
- String类用法总结
String类在编程中出现的频率是非常高的,熟练掌握是很有必要的 一.常用方法总结: 获取方法 1.1:字符串中包含的字符数,也就是字符串的长度. int length():获取长度 1.2:根据位置 ...
- iOS中 UIWebView加载网络数据 技术分享
直奔核心: #import "TechnologyDetailViewController.h" #define kScreenWidth [UIScreen mainScreen ...
- PO核准通知界面修改
想在notification頁面把供應商的稅捐代碼帶出來,添在如下紅框中 PO_WF_PO_NOTIFICATION head information:get_po_approve_msg line ...
- Scipy教程 - 统计函数库scipy.stats
http://blog.csdn.net/pipisorry/article/details/49515215 统计函数Statistical functions(scipy.stats) Pytho ...
- Swing——动作(Action)
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41258997 Action接口扩展于ActionListe ...