今天我们来一起学习一下缓存技术,相信大家做开发的时候都知道请求网络数据的重要,但是有一些只用请求一次就过时性的消息比如某些新闻信息,如果我们每次进入新闻界面就从新从网络上获取势必会给用户带来不好的体验,所以我们需要缓存技术来帮我们解决这一问题。
1,LruCache介绍
核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
2,LruCache使用 下面我们就来写一个简单的demo来学习LruCache,效果也是每次请求一次第二次直接从缓存中提取出来不用再次请求网络[?]
/**
* 缓存json数据
*/
private LruCache<integer, string=""> mJsonCache;
/**
* 缓存图片信息
*/
private LruCache<integer, bitmap=""> mBitmapCache;
public Util() {
mJsonCache = new LruCache<integer, string="">(1 * 1024 * 1024);
mBitmapCache = new LruCache<integer, bitmap="">(2 * 1024 * 1024);
}
/**
* 添加进入缓存列表
*
* @param key
* @param value
*/
public void addJsonLruCache(Integer key, String value) {
mJsonCache.put(key, value);
}
public void addBitmapLruCache(Integer key, Bitmap value) {
mBitmapCache.put(key, value);
}
/**
* 从缓存列表中拿出来
*
* @param key
* @return
*/
public String getJsonLruCache(Integer key) {
return mJsonCache.get(key);
}
public Bitmap getBitmapLruCache(Integer key) {
return mBitmapCache.get(key);
}
可以看到我们准备缓存Bitmap与String,只需要拿到信息的时候put进缓存中,需要的时候get出来,是不是非常简单,我们为我们String分配了1m为我们的Bitmap分配了2m空间,这只是我们的demo为了简单这样使用,实际上我们应该更加详细的考虑到底应该为缓存分配多大的空间[?]
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024);
一般来说最大值的1/8左右就可以了。?
public class MainActivity extends Activity implements OnItemClickListener {
private static final String LIST_DATA = http://api.yi18.net/top/list;
private ListView mListView;
private ArrayAdapter<string> mAdapter;
private ArrayList<integer> mListId;
private Util util;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
util = new Util();
mListView = (ListView) findViewById(R.id.list);
mListId = new ArrayList<integer>();
mAdapter = new ArrayAdapter<string>(this,
android.R.layout.simple_list_item_1);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
new DownLoadJson().execute(LIST_DATA);
}
这一段就是普通的请求数据添加到ListView中。
private void getJsonData(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
if (jsonObject.getBoolean(success)) {
JSONArray jsonArray = jsonObject.getJSONArray(yi18);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject2 = (JSONObject) jsonArray.opt(i);
if (i < 5) {
mAdapter.add(jsonObject2.getString(title));
mListId.add(jsonObject2.getInt(id));
}
}
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
class DownLoadJson extends AsyncTask<string, string=""> {
@Override
protected String doInBackground(String... params) {
return util.downLoadJson(params[0]);
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
getJsonData(result);
}
}
}
3,缓存
@Override
public void onItemClick(AdapterView<!--?--> arg0, View arg1, int arg2, long arg3) {
// TODO Auto-generated method stub
String message = util.getJsonLruCache(mListId.get(arg2));
Bitmap bitmap = util.getBitmapLruCache(mListId.get(arg2));
if (message != null) {
intentNewsInfo(arg2, message, bitmap);
} else {
intentNewsInfo(arg2, null, null);
}
}
public void intentNewsInfo(int arg2, String message, Bitmap bitmap) {
Intent intent = new Intent(MainActivity.this, NewsinfoActivity.class);
intent.putExtra(message, message);
intent.putExtra(bitmap, bitmap);
intent.putExtra(index, arg2);
intent.putExtra(id, mListId.get(arg2));
startActivityForResult(intent, 100);
}
可以看到我们这里先是查找缓存中是否存在数据如果存在直接传给新闻详情界面,如果没有则在第二个界面获取再传回来。
public class NewsinfoActivity extends Activity {
private String NEWS_INFO = http://api.yi18.net/top/show?id=;
private String imageRes[] = {
http://d.hiphotos.baidu.com/image/h%3D360/sign=405b763459afa40f23c6c8db9b65038c/562c11dfa9ec8a13508c96e6f403918fa0ecc026.jpg,
http://c.hiphotos.baidu.com/image/h%3D360/sign=798b4f82caea15ce5eeee60f86013a25/9c16fdfaaf51f3dece3f986397eef01f3a297923.jpg,
http://f.hiphotos.baidu.com/image/h%3D360/sign=20a94e03940a304e4d22a6fce1c9a7c3/ac4bd11373f082028719ab3848fbfbedab641b29.jpg,
http://b.hiphotos.baidu.com/image/h%3D360/sign=3a1af7349145d688bc02b4a294c37dab/4b90f603738da977c0f5b82cb351f8198718e3db.jpg,
http://d.hiphotos.baidu.com/image/h%3D360/sign=75e596560f33874483c5297a610ed937/55e736d12f2eb9381891b2f4d6628535e5dd6f3c.jpg };
private Intent intent;
private Util util;
private int newId, index;
private ImageView imageView;
private TextView textView;
private Bitmap bitmap;
private String message;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_newsinfo);
intent = getIntent();
util = new Util();
imageView = (ImageView) findViewById(R.id.image);
textView = (TextView) findViewById(R.id.message);
newId = intent.getExtras().getInt(id);
index = intent.getExtras().getInt(index);
if (intent.getExtras().getString(message) != null) {
message = intent.getExtras().getString(message);
bitmap = intent.getParcelableExtra(bitmap);
textView.setText(Html.fromHtml(message));
imageView.setImageBitmap(bitmap);
Toast.makeText(this, 没有访问网络哦, 2000).show();
} else {
new DownLoadJson().execute(NEWS_INFO + newId);
new DownLoadBitmap().execute(imageRes[index]);
Toast.makeText(this, 访问网络哦, 2000).show();
}
}
@Override
public void onBackPressed() {
Intent dataIntent = new Intent();
dataIntent.putExtra(message, message);
dataIntent.putExtra(bitmap, bitmap);
dataIntent.putExtra(newId, newId);
setResult(20, dataIntent);
finish();
super.onBackPressed();
}
private void getJsonData(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
if (jsonObject.getBoolean(success)) {
JSONObject jsonObject2 = new JSONObject(
jsonObject.getString(yi18));
message = jsonObject2.getString(message);
textView.setText(Html.fromHtml(jsonObject2.getString(message)));
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
class DownLoadJson extends AsyncTask<string, string=""> {
@Override
protected String doInBackground(String... params) {
return util.downLoadJson(params[0]);
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
getJsonData(result);
}
}
}
class DownLoadBitmap extends AsyncTask<string, bitmap=""> {
@Override
protected Bitmap doInBackground(String... params) {
// TODO Auto-generated method stub
return util.downLoadBitmap(params[0]);
}
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
bitmap = result;
imageView.setImageBitmap(result);
}
}
}
}
这就比较清晰明白了,每次我们都把这个界面获取到的信息存到LruCache里面。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
int newId = data.getExtras().getInt(newId);
String message = data.getExtras().getString(message);
Bitmap bitmap = data.getParcelableExtra(bitmap);
util.addJsonLruCache(newId, message);
util.addBitmapLruCache(newId, bitmap);
super.onActivityResult(requestCode, resultCode, data);
}
4,效果
5.Android LruCache源码介绍
package android.util;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
* become eligible for garbage collection.
* Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
* 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
* <p>If your cached values hold resources that need to be explicitly released,
* override {@link #entryRemoved}.
* 如果你cache的某个值需要明确释放,重写entryRemoved()
* <p>If a cache miss should be computed on demand for the corresponding keys,
* override {@link #create}. This simplifies the calling code, allowing it to
* assume a value will always be returned, even when there's a cache miss.
* 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。
* <p>By default, the cache size is measured in the number of entries. Override
* {@link #sizeOf} to size the cache in different units. For example, this cache
* is limited to 4MiB of bitmaps: 默认cache大小是测量的item的数量,重写sizeof计算不同item的
* 大小。
* <pre> {@code
* int cacheSize = 4 * 1024 * 1024; // 4MiB
* LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
* protected int sizeOf(String key, Bitmap value) {
* return value.getByteCount();
* }
* }}</pre>
*
* <p>This class is thread-safe. Perform multiple cache operations atomically by
* synchronizing on the cache: <pre> {@code
* synchronized (cache) {
* if (cache.get(key) == null) {
* cache.put(key, value);
* }
* }}</pre>
*
* <p>This class does not allow null to be used as a key or value. A return
* value of null from {@link #get}, {@link #put} or {@link #remove} is
* unambiguous: the key was not in the cache.
* 不允许key或者value为null
* 当get(),put(),remove()返回值为null时,key相应的项不在cache中
*/
public class LruCache<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 LruCache(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.eldest();
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);
}
}