这两天工作之余,刷了一些博客,了解了android利用缓存的一些东西,想必大家也都有一些相关的经验,新手不了解也没有关系,本博文就给大家介绍一下缓存的使用。
——————————————————我是分割线————————————————
首先简单介绍一下内缓存,图片加载使用内缓存最多的应该是LruCache,我们可以对这个类设置泛型LruCache
mInnerCache = new LruCache<String, Bitmap>(1*1024*1024){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight();
}
};
构造中的参数是我们需要指定的内存缓存的大小,注意这里我们需要 重写sizeOf方法,它表示了如何计算一个value值得大小,不重写的话sizeOf默认返回1,也就是说没有起到控制bitmap占用内存大小的功能。
然后我们在从某些途径(网络或文件系统)获得了bitmap时,直接mInnerCache.put就可以加入缓存,获取的时候get就行,它其实就是维护了一个特殊的map,对map进行了大小控制得到的功能。
这当然不是我们今天的重点!!!!!
然后我们再来简单看看硬盘缓存。
恩~我需要一个盗链:郭前辈的Android DiskLruCache完全解析,硬盘缓存的最佳方案,其中讲了硬盘缓存的使用方式。
这当然也不是我们的重点!!!
郭前辈也写过融合内存缓存和硬盘缓存的方法,在研究了他得融合方案基础上,我有些自己的想法,于是尝试做一套自己的缓存策略。
郭前辈的缓存策略是:需要获取图片时,先直接从网上下载图片至硬盘缓存中,再将硬盘缓存中的图片二次读取,并加入内存缓存中,下一次想获得该图片,先从内存缓存中找,没有的话开辟异步任务,从硬缓存中找,还没有的话操作回到开始。
我的缓存策略是: 需要获取图片时,从网络上下载至内存,加入到内存缓存,不断的向内存中写入,当内存缓存满时,将需要清理出内存缓存的bitmap缓存到硬盘;再次需要该图片时,先问内存要,没有的话开辟异步任务问硬盘要,要到了就加入内存缓存,没要到就重新从网上下载。
可能读起来有点拗口,仔细理解一下,我觉得这样更符合计算机局部化规则,也更有利于我们节省流量。
现在到了今天的重点了!
要实现本文中的缓存策略无疑是有难度的,难点有二:
1、如何获取LruCache即将清理掉的bitmap;
2、如何将内存中的bitmap写入硬盘。
要解决这两个问题我们得从LruCache的源码入手:
package com.example.volleydemo.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class Lru<K, V> {
private final LinkedHashMap<K, V> map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size; // 已经存储的大小
private int maxSize; // 规定的最大存储空间
private int putCount; // put的次数
private int createCount; // create的次数
private int evictionCount; // 回收的次数
private int hitCount; // 命中的次数
private int missCount; // 丢失的次数
/**
* @param maxSize
* for caches that do not override {@link #sizeOf}, this is the
* maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this
* cache.
*/
public Lru(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created. 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
* 如果item的value没有被cache或者不能被创建,则返回null。
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++; // 命中
return mapValue;
}
missCount++; // 丢失
}
/*
* Attempt to create a value. This may take a long time, and the map may
* be different when create() returns. If a conflicting value was added
* to the map while create() was working, we leave that value in the map
* and release the created value. 如果丢失了就试图创建一个item
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;// 创建++
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
// 如果前面存在oldValue,那么撤销put()
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) { // 返回的先前的value值
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/**
* @param maxSize
* the maximum size of the cache before returning. May be -1 to
* evict even 0-sized elements. 清空cache空间
*/
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
/**
* Removes the entry for {@code key} if it exists. 删除key相应的cache项,返回相应的value
*
* @return the previous value mapped by {@code key}.
*/
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
* 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用, 或者替换item值时put调用,默认实现什么都没做。
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* @param evicted
* true if the entry is being removed to make space, false if the
* removal was caused by a {@link #put} or {@link #remove}.
* true---为释放空间被删除;false---put或remove导致
* @param newValue
* the new value for {@code key}, if it exists. If non-null, this
* removal was caused by a {@link #put}. Otherwise it was caused
* by an eviction or a {@link #remove}.
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null. 当某Item丢失时会调用到,返回计算的相应的value或者null
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* <p>
* If a value for {@code key} exists in the cache when this method returns,
* the created value will be released with {@link #entryRemoved} and
* discarded. This can occur when multiple threads request the same key at
* the same time (causing multiple values to be created), or when one thread
* calls {@link #put} while another is creating a value for the same key.
*/
protected V create(K key) {
return null;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "="
+ value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size is
* the number of entries and max size is the maximum number of entries.
* 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
* <p>
* An entry's size must not change while it is in the cache.
*/
protected int sizeOf(K key, V value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
* 清空cacke
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value that was
* already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted. 返回被回收的数量
*/
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* Returns a copy of the current contents of the cache, ordered from least
* recently accessed to most recently accessed. 返回当前cache的副本,从最近最少访问到最多访问
*/
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override
public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format(
"LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize,
hitCount, missCount, hitPercent);
}
}
这是我在网上找到的LruCache的源码,确实挺短的,加上一些有的没的才300+行,我们关注一下这个方法:
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) { // 返回的先前的value值
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
因为需要得到即将清理出内存缓存的bitmap,就得知道什么时候会出现这种情况,只有一种!就是新项加入到cache中,cache发现大小超了,就去清理门户了。于是我们很容易知道清理门户的方法是trimToSize(maxSize)。我们就来研究一下:
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
最后的 entryRemoved方法默认空实现,我们先不管它,机制的读者直接瞄准了remove方法,就是你了皮卡丘,map的remove方法自然会为我们返回这个可怜的即将被清理的bitmap,咳咳,也就是 map.remove(key);上面一行的value ,有了key有了value,想要干什么不容易?
于是第一个问题解决了!
那么第二个问题,怎么将一个bitmap写入硬盘,其实也很简单,Bitmap有一个compress方法,用它可以直接进行图片的压缩写入工作。
这里,我们队LruCache进行改造,改造的方法也很简单:
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//---------把将要移出内缓存的bitmap加入硬盘缓存中
addToOutterCache(key,value);
//---------把将要移出内缓存的bitmap加入硬盘缓存中
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
将下面这两个方法直接加入LruCache类中:
private void addToOutterCache(String key,Bitmap value) {
try {
if(outterCache.get(getMd5(key)) != null){
Log.v("addToOutterCache", "不用写入");
return;
}
} catch (NoSuchAlgorithmException | IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Log.v("addToOutterCache", "写入硬盘缓存");
DiskLruCache.Editor editor = outterCache.edit(getMd5(key));
OutputStream os=editor.newOutputStream(0);
value.compress(Bitmap.CompressFormat.PNG, 0, os);
editor.commit();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getMd5(String str) throws NoSuchAlgorithmException {
final MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(str.getBytes());
StringBuilder sb = new StringBuilder(40);
for(byte x:bs) {
if((x & 0xff)>>4 == 0) {
sb.append("0").append(Integer.toHexString(x & 0xff));
} else {
sb.append(Integer.toHexString(x & 0xff));
}
}
return sb.toString();
}
好了 我们的LruCache改造工程完成了,也就去去几十行,给新类起个名字叫BitmapLru。
下面贴一下我们工程的代码:
MainActivity还要吗?。。。。
public class MainActivity extends Activity {
ListView listview;
CacheAdapter adapter;
List<String> datalist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
datalist=new ArrayList<String>();
for(String s:Urldata.imageUrls){
datalist.add(s);
}
listview=(ListView) findViewById(R.id.listview);
adapter=new CacheAdapter(this,datalist);
listview.setAdapter(adapter);
}
}
CacheAdapter:
public class CacheAdapter extends BaseAdapter {
LayoutInflater infalter;
List<String> datalist;
BitmapLru mInnerCache;
DiskLruCache mOutterCache;
BitmapFactory.Options opt;
public CacheAdapter(Context c, List<String> datalist) {
super();
this.infalter = LayoutInflater.from(c);
this.datalist = datalist;
opt=new Options();
opt.inPreferredConfig=Config.RGB_565;
try {
mOutterCache = DiskLruCache.open(getDiskCacheDir(c, "bitmap"),
1, 1, 5 * 1024 * 1024);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mInnerCache = new BitmapLru(3*1024*1024,mOutterCache){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight();
}
};
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return datalist.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return datalist.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = infalter.inflate(R.layout.list_item, null);
holder.icon = (ImageView) convertView.findViewById(R.id.News_icon);
holder.tittle = (TextView) convertView
.findViewById(R.id.News_Tittle);
holder.content = (TextView) convertView
.findViewById(R.id.News_Content);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setTag(datalist.get(position));
holder.icon.setBackgroundResource(R.drawable.ic_launcher);
holder.tittle.setText("hello " + position);
holder.content.setText("fine " + position);
loadImage(holder.icon, datalist.get(position));
return convertView;
}
private void loadImage(ImageView icon, String url) {
Bitmap image = getFromInnerCache(url);
if (image == null) {
new GrobImage(icon).execute(url);
} else {
Log.e("loadImage","从内存缓存中获取");
icon.setImageBitmap(image);
}
}
private Bitmap getFromInnerCache(String url) {
// TODO Auto-generated method stub
return mInnerCache.get(url);
}
class ViewHolder {
ImageView icon;
TextView tittle;
TextView content;
}
class GrobImage extends AsyncTask<String, Void, Bitmap> {
private String imageUrl;
private ImageView view;
public GrobImage(ImageView view){
this.view=view;
}
@Override
protected Bitmap doInBackground(String... params) {
// TODO Auto-generated method stub
FileInputStream fileInputStream = null;
imageUrl = params[0];
Bitmap image = null;
Snapshot snapShot = null;
try {
snapShot = mOutterCache.get(getMd5(imageUrl));
if(snapShot!=null){
//从硬盘缓存中得到了数据
Log.e("doInBackground", "从硬盘缓存中找到数据");
fileInputStream=(FileInputStream) snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fileInputStream.getFD();
image=BitmapFactory.decodeFileDescriptor(fileDescriptor);
fileInputStream.close();
if(image!=null){
mInnerCache.put(imageUrl, image);
}
}else{
//需要在网络中获取
Log.e("doInBackground", "网络中获取");
URL url=new URL(imageUrl);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
image=BitmapFactory.decodeStream(conn.getInputStream(), null, opt);
mInnerCache.put(imageUrl, image);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return image;
}
@Override
protected void onPostExecute(Bitmap result) {
if (view != null && result != null) {
view.setImageBitmap(result);
}
super.onPostExecute(result);
}
}
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
public String getMd5(String str) throws NoSuchAlgorithmException {
final MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(str.getBytes());
StringBuilder sb = new StringBuilder(40);
for(byte x:bs) {
if((x & 0xff)>>4 == 0) {
sb.append("0").append(Integer.toHexString(x & 0xff));
} else {
sb.append(Integer.toHexString(x & 0xff));
}
}
return sb.toString();
}
}
BitmapLru:
public class BitmapLru {
private final LinkedHashMap<String, Bitmap> map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size; // 已经存储的大小
private int maxSize; // 规定的最大存储空间
private int putCount; // put的次数
private int createCount; // create的次数
private int evictionCount; // 回收的次数
private int hitCount; // 命中的次数
private int missCount; // 丢失的次数
//---------------------------------------------------------------------
DiskLruCache outterCache;
public BitmapLru(int maxSize,DiskLruCache outterCache) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.outterCache=outterCache;
this.maxSize = maxSize;
this.map = new LinkedHashMap<String,Bitmap>(0, 0.75f, true);
}
//---------------------------------------------------------------------
/**
* @param maxSize
* for caches that do not override {@link #sizeOf}, this is the
* maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this
* cache.
*/
public BitmapLru(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String,Bitmap>(0, 0.75f, true);
}
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created. 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
* 如果item的value没有被cache或者不能被创建,则返回null。
*/
public final Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++; // 命中
return mapValue;
}
missCount++; // 丢失
}
/*
* Attempt to create a value. This may take a long time, and the map may
* be different when create() returns. If a conflicting value was added
* to the map while create() was working, we leave that value in the map
* and release the created value. 如果丢失了就试图创建一个item
*/
Bitmap createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;// 创建++
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
// 如果前面存在oldValue,那么撤销put()
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final Bitmap put(String key, Bitmap value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
Bitmap previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) { // 返回的先前的value值
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/**
* @param maxSize
* the maximum size of the cache before returning. May be -1 to
* evict even 0-sized elements. 清空cache空间
*/
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//---------把将要移出内缓存的bitmap加入硬盘缓存中
addToOutterCache(key,value);
//---------把将要移出内缓存的bitmap加入硬盘缓存中
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
//-----------------------改写部分------------------------
private void addToOutterCache(String key,Bitmap value) {
try {
if(outterCache.get(getMd5(key)) != null){
Log.v("addToOutterCache", "不用写入");
return;
}
} catch (NoSuchAlgorithmException | IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Log.v("addToOutterCache", "写入硬盘缓存");
DiskLruCache.Editor editor = outterCache.edit(getMd5(key));
OutputStream os=editor.newOutputStream(0);
value.compress(Bitmap.CompressFormat.PNG, 0, os);
editor.commit();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getMd5(String str) throws NoSuchAlgorithmException {
final MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(str.getBytes());
StringBuilder sb = new StringBuilder(40);
for(byte x:bs) {
if((x & 0xff)>>4 == 0) {
sb.append("0").append(Integer.toHexString(x & 0xff));
} else {
sb.append(Integer.toHexString(x & 0xff));
}
}
return sb.toString();
}
//-----------------------改写部分------------------------
/**
* Removes the entry for {@code key} if it exists. 删除key相应的cache项,返回相应的value
*
* @return the previous value mapped by {@code key}.
*/
public final Bitmap remove(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
* 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用, 或者替换item值时put调用,默认实现什么都没做。
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* @param evicted
* true if the entry is being removed to make space, false if the
* removal was caused by a {@link #put} or {@link #remove}.
* true---为释放空间被删除;false---put或remove导致
* @param newValue
* the new value for {@code key}, if it exists. If non-null, this
* removal was caused by a {@link #put}. Otherwise it was caused
* by an eviction or a {@link #remove}.
*/
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null. 当某Item丢失时会调用到,返回计算的相应的value或者null
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* <p>
* If a value for {@code key} exists in the cache when this method returns,
* the created value will be released with {@link #entryRemoved} and
* discarded. This can occur when multiple threads request the same key at
* the same time (causing multiple values to be created), or when one thread
* calls {@link #put} while another is creating a value for the same key.
*/
protected Bitmap create(String key) {
return null;
}
private int safeSizeOf(String key, Bitmap value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "="
+ value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size is
* the number of entries and max size is the maximum number of entries.
* 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
* <p>
* An entry's size must not change while it is in the cache.
*/
protected int sizeOf(String key, Bitmap value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
* 清空cacke
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value that was
* already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted. 返回被回收的数量
*/
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* Returns a copy of the current contents of the cache, ordered from least
* recently accessed to most recently accessed. 返回当前cache的副本,从最近最少访问到最多访问
*/
public synchronized final Map<String, Bitmap> snapshot() {
return new LinkedHashMap<String, Bitmap>(map);
}
@Override
public synchronized final java.lang.String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return java.lang.String.format(
"LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize,
hitCount, missCount, hitPercent);
}
}
DiskLruCache这个类不贴了,工具类,郭前辈的在我前面提到的博文中也给出来了,还有图片的url,我无耻的使用了郭前辈的相册。
!!!!!最后说一下注意事项!!!!!!!!!:
由于硬盘缓存的操作牵扯到journal文件的读写,所以多线程操作很可能引起冲突,这里我们为什么可以这么做,因为我们使用的是AsyncTask异步任务,AsyncTask在android3.0以后不是并发的,等于是一个大小为1的线程池,所以在AsyncTask中操作不会引起冲突。
这种实现方式也有缺点:没有克服大批量IO的问题(谁克服了?)。我工程中也没做图片的闪烁处理,原理上说就是对异步任务的cancel,不是主要矛盾嘛,嘿嘿~见谅。
可能还有很多代码问题,希望指正,谢谢。