在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。Android为我们提供了LruCache,今天我们就来学习这个缓存的知识以及原理。
LruCache缓存的实例代码
一、 我们建立一个简单的项目去体会LruCache的使用过程
通过http请求网络上的图片文件,然后保存在缓存中。显示图片时,先从缓存中取,如果没有,就发送请求向服务器取。项目结构如下:
二、 在AndroidManifest.xml文件中,加入网络权限的声明:
三、 创建一个图片加载的类,用于对缓存的一些操作,重写LruCache的sizeOf方法:
package com.example.linux.lrucachetest;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
*Created by huhx on 2016/4/12.
*/
public class ImageDownloader {
private static final String TAG = "TextDownload";
private LruCachelruCache;
public ImageDownloader() {
long maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemory / 8);
lruCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}
// 把Bitmap对象加入到缓存中
public void addBitmapToMemory(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
lruCache.put(key, bitmap);
}
}
// 从缓存中得到Bitmap对象
public Bitmap getBitmapFromMemCache(String key) {
Log.i(TAG, "lrucache size: " + lruCache.size());
return lruCache.get(key);
}
// 从缓存中删除指定的Bitmap
public void removeBitmapFromMemory(String key) {
lruCache.remove(key);
}
}
四、 在MainActivity中使用并测试LruCache:showBitmap方法是先从缓存中取,如果没有就发送http请求取得。
public void showBitmap(View view) {
Bitmap bitmap = imageDownloader.getBitmapFromMemCache("bitmap");
if (bitmap == null) {
new BitmapThread(bitmapUrl).start();
} else {
imageView.setImageBitmap(bitmap);
}
}
五、 BitmapThread的线程:从服务器拿到Bitmap对象,并加入到缓存中。
class BitmapThread extends Thread {
private String bitmapUrl;
BitmapThread(String bitmapUrl) {
this.bitmapUrl = bitmapUrl;
}
@Override
public void run() {
Log.i(TAG, "run: " + Thread.currentThread().getName());
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
URL url = new URL(bitmapUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
}
imageDownloader.addBitmapToMemory("bitmap", bitmap);
handler.obtainMessage(DOWNLOAD_IMAGE, bitmap).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
六、 handler处理消息,并显示图片:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "hanlder handleMessage: " + Thread.currentThread().getName());
switch (msg.what) {
case DOWNLOAD_IMAGE:
imageView.setImageBitmap((Bitmap) msg.obj);
break;
}
}
};
七、 从缓存中删除图片:
publicvoid remove(View view) {
imageDownloader.removeBitmapFromMemory("bitmap");
}
LruCache缓存的原理分析
通过上述的案例,我们已经知道了LruCache的使用方法。接下来,我们一步步的分析它的过程以及原理。
一、 LruCache的文档描述如下:
A cache that holds strong references to a limited number of values. Each time a valueisaccessed, itismoved to the head of a queue. When a valueisadded to a full cache, the value at the end of that queueisevicted and may become eligibleforgarbage collection.
二、 它的属性一方法说明如下:
publicclassLruCache {
privatefinalLinkedHashMap map;
/** Size of this cache in units. Not necessarily the number of elements. */privateint size;
privateint maxSize;
privateint putCount;
privateint createCount;
privateint evictionCount;
privateint hitCount;
privateint missCount;
}
文档上一些对LruCache方法的描述:
If your cached values hold resources that need to be explicitly released,overrideentryRemoved(boolean, K, V, V)
If a cache miss should be computed on demand
forthe corresponding keys,overridecreate(K). This simplifies the calling code, allowing it to assume a value will always be returned, even when there's a cache miss.
Bydefault, the cache sizeismeasuredinthe number of entries. Override sizeOf(K, V) to size the cacheindifferent units. For example,thiscacheislimited to 4MiB of bitmaps:
三、 LruCache只有一个构造方法,LruCache(int maxSize)代码如下:初始化一个LinkedHashMap
publicLruCache(int maxSize) {
if(maxSize <= 0) {
thrownewIllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map =newLinkedHashMap(0, 0.75f,true);
}
四、 LruCache的put方法是把内容放入到缓存中去,代码如下:
publicfinal V put(K key, V value) {
if(key ==null|| value ==null) {
thrownewNullPointerException("key == null || value == null");
}
V previous;
synchronized(this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if(previous !=null) {
size -= safeSizeOf(key, previous);
}
}
if(previous !=null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
其中safeSizeOf方法,是计算LruCache的已经缓存的大小,以下的sizeOf(默认返回1)方法是我们要重写的。
privateint safeSizeOf(K key, V value) {
intresult = sizeOf(key, value);
if(result < 0) {
thrownewIllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
我们要重写sizeOf方法:
protectedint sizeOf(K key, V value) {
return1;
}
五、 LruCache的get方法是从缓存中去取得内容,代码如下:
publicfinal V get(K key) {
if(key ==null) {
thrownewNullPointerException("key == null");
}
V mapValue;
synchronized(this) {
// 如果根据相应的key得到value,就增加一次命中hitCount,并且返回结果 mapValue
= map.get(key);
if(mapValue !=null) {
hitCount++;
returnmapValue; }
// 否则增加一次missCountmissCount
++;
}
/* * 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.
*///试图根据这个key,创建一个value。这里的create(key)默认是返回null,当然这个方法是可以重写的V createdValue= create(key);
if(createdValue ==null) {
returnnull;
}
synchronized(this) {
createCount++;
//如果我们重写了create(key)方法而且返回值不为空,那么将上述的key与这个返回值写入到map当中mapValue
= map.put(key, createdValue);
if(mapValue !=null) {
//There was a conflict so undo that last put
// 方法放入最后put的key,value值
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if(mapValue !=null) {
// 这个方法也可以重写 entryRemoved(
false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
六、 LruCache的remove方法是从缓存中去删除内容,并更新已经缓存的大小,代码如下:
publicfinal V remove(K key) {
if(key ==null) {
thrownewNullPointerException("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;
}