上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,如今好好理一下吧。

Android中普通情况下採取的缓存策略是使用二级缓存。即内存缓存+硬盘缓存—>LruCache+DiskLruCache。二级缓存能够满足大部分的需求了,另外还有个三级缓存(内存缓存+硬盘缓存+网络缓存),当中DiskLruCache就是硬盘缓存,下篇再讲吧!

1、那么LruCache究竟是什么呢?

查了下官方资料。是这样定义的:

LruCache 是对限定数量的缓存对象持有强引用的缓存,每一次缓存对象被訪问,都会被移动到队列的头部。当有对象要被加入到已经达到数量上限的 LruCache 中,队列尾部的对象将会被移除,并且可能会被垃圾回收器回收。LruCache 中的
Lru 指的是“Least Recently Used-最近最少使用算法”。

这就意味着,LruCache 是一个能够推断哪个缓存对象是最近最少使用的缓存对象。从而把最少使用的移至队尾,而最近使用的则留在队列前面了。举个样例:比方我们有a、b、c、d、e五个元素,而a、c、d、e都被訪问过。唯有b元素没有訪问,则b元素就成为了最近最少使用元素了,就移至在队尾了。

从上面的叙述中我们总结能够知道LruCache核心思想就两点:

1、LruCache使用的是最近最少使用算法。最近使用最少的将会移至到队尾。而最近刚刚使用的则会移至到头部。

2、LruCache缓存的大小是限定的(限定的大小由我们定义),当LruCache存储空间满了就会移除队尾的而为新的对象的加入腾出控件。

2、我们还是从源代码入手学学LruCache究竟怎么使用吧:

我们先看看LruCache类中的变量有什么:

显示发现一堆int类型的变量。另一个最重要的LinkedHashMap<K,V> 这个队列,通俗的讲LinkedHashMap<K,V>就是一个双向链表存储结构。

各个变量的意思为:

size - LruCache中已经存储的大小

maxSize - 我们定义的LruCache缓存最大的空间

putCount - put的次数(为LruCache加入缓存对象的次数)

createCount - create的次数

evictionCount - 回收的次数

hitCount - 命中的次数

missCount - 丢失的次数

再看看构造器:

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);
}

发现须要传入一个int类型的值。顾名思义。这就是我们定义的LruCache缓存的空间大小了,普通情况下我们能够得到应用程序的最大可用空间,然后按百分比取值设置给它就可以。

再看看其他一些比較重要的方法:

put()方法:

 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) {
size -= safeSizeOf(key, previous);
}
} if (previous != null) {
entryRemoved(false, key, previous, value);
} trimToSize(maxSize);
return previous;
}

通过该方法我们能够知道LruCache中是通过<Key,Value>形式存储缓存数据的。

意思就是我们把一个Value存储到LruCache中,并设置相应键值为key。然后推断key和value都不能为空,否则就抛异常了。之后把该Value移至队列的头部。

get()方法:

    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.
*/ 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
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
} if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}

该方法就是得到相应key缓存的Value,假如该Value存在,返回Value并且移至该Value至队列的头部,这也证实了最近最先使用的将会移至队列的头部。

假如Value不存在则返回null。

remove()方法:

    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;
}

该方法就是从LruCache缓存中移除相应key的Value值。

sizeof()方法:一般须要重写的:

 protected int sizeOf(K key, V value) {
return 1;
}

重写它计算不同的Value的大小。

一般我们会这样重写:

mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
if(bitmap!=null){
return bitmap.getByteCount();
}
return 0;
}
};

好了,总结一下使用LruCache的原理:比方像ImageView中载入一张图片时候,首先会在LruCache的缓存中检查是否有相应的key值(get( key)),假设有就返回相应的Bitmap。从而更新ImageView。假设没有则又一次开启一个异步线程来又一次载入这张图片。

来看看用LruCache缓存Bitmap的样例:

public class MyLruCache extends AppCompatActivity{
private LruCache<String,Bitmap> mLruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//得到应用程序最大可用内存
int maxCache = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
if(bitmap!=null){
return bitmap.getByteCount();
}
return 0;
}
};
}
/**
* 加入Bitmap到LruCache中
*
* @param key
* @param bitmap
*/
public void putBitmapToLruCache(String key, Bitmap bitmap) {
if (getBitmapFromLruCache(key) == null) {
mLruCache.put(key, bitmap);
}
} /**
* @param key
* @return 从LruCache缓存中获取一张Bitmap。没有则会返回null
*/
public Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
}

以下通过一个实例来看看怎么用:

先看看效果吧:

贴下主要代码:

MainActivity:

public class MainActivity extends ActionBarActivity {
private GridView mGridView;
private List<String> datas;
private Toolbar mToolbar;
private GridViewAdapter mAdapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("zxy", "cache:" + getCacheDir().getPath());
Log.v("zxy", "Excache:" + getExternalCacheDir().getPath());
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mToolbar.setTitleTextColor(Color.WHITE);
mToolbar.setNavigationIcon(R.mipmap.icon);
setSupportActionBar(mToolbar);
initDatas(); mGridView = (GridView) findViewById(R.id.gridView);
mAdapter = new GridViewAdapter(this, mGridView, datas);
mGridView.setAdapter(mAdapter);
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "position=" + position + ",id=" + id, Toast.LENGTH_SHORT).show();
}
}); } public void initDatas() {
datas = new ArrayList<>();
for (int i = 0; i < 55; i++) {
datas.add(URLDatasTools.imageUrls[i]);
}
} @Override
protected void onDestroy() {
super.onDestroy();
mAdapter.cancelAllDownloadTask();//取消全部下载任务
}
}

GridViewAdapter:

public class GridViewAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
private List<DownloadTask> mDownloadTaskList;//全部下载异步线程的集合
private Context mContext;
private GridView mGridView;
private List<String> datas;
private LruCache<String, Bitmap> mLruCache;
private int mFirstVisibleItem;//当前页显示的第一个item的位置position
private int mVisibleItemCount;//当前页共显示了多少个item
private boolean isFirstRunning = true; public GridViewAdapter(Context context, GridView mGridView, List<String> datas) {
this.mContext = context;
this.datas = datas;
this.mGridView = mGridView;
this.mGridView.setOnScrollListener(this);
mDownloadTaskList = new ArrayList<>();
initCache();
} private void initCache() {
//得到应用程序最大可用内存
int maxCache = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
if (bitmap != null) {
return bitmap.getByteCount();
}
return 0;
}
};
} @Override
public int getCount() {
return datas.size();
} @Override
public Object getItem(int position) {
return datas.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_item, parent, false);
ImageView mImageView = (ImageView) convertView.findViewById(R.id.imageView);
TextView mTextView = (TextView) convertView.findViewById(R.id.textView);
String url = datas.get(position);
mImageView.setTag(String2MD5Tools.hashKeyForDisk(url));//设置一个Tag为md5(url),保证图片不错乱显示
mTextView.setText("第" + position + "项");
setImageViewForBitmap(mImageView, url);
return convertView; } /**
* 给ImageView设置Bitmap
*
* @param imageView
* @param url
*/
private void setImageViewForBitmap(ImageView imageView, String url) {
String key = String2MD5Tools.hashKeyForDisk(url);//对url进行md5编码
Bitmap bitmap = getBitmapFromLruCache(key);
if (bitmap != null) {
//假设缓存中存在。那么就设置缓存中的bitmap
imageView.setImageBitmap(bitmap);
} else {
//不存在就设置个默认的背景色
imageView.setBackgroundResource(R.color.color_five);
}
} /**
* 加入Bitmap到LruCache中
*
* @param key
* @param bitmap
*/
public void putBitmapToLruCache(String key, Bitmap bitmap) {
if (getBitmapFromLruCache(key) == null) {
mLruCache.put(key, bitmap);
}
} /**
* @param key
* @return 从LruCache缓存中获取一张Bitmap,没有则会返回null
*/
public Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {//GridView为精巧状态时,让它去下载图片
loadBitmap(mFirstVisibleItem, mVisibleItemCount);
} else {
//滚动时候取消全部下载任务
cancelAllDownloadTask();
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
if (isFirstRunning && visibleItemCount > 0) {//首次进入时载入图片
loadBitmap(mFirstVisibleItem, mVisibleItemCount);
isFirstRunning = false;
}
} /**
* 载入图片到ImageView中
*
* @param mFirstVisibleItem
* @param mVisibleItemCount
*/
private void loadBitmap(int mFirstVisibleItem, int mVisibleItemCount) {
//首先推断图片在不在缓存中,假设不在就开启异步线程去下载该图片
for (int i = mFirstVisibleItem; i < mFirstVisibleItem + mVisibleItemCount; i++) {
final String url = datas.get(i);
String key = String2MD5Tools.hashKeyForDisk(url);
Bitmap bitmap = getBitmapFromLruCache(key);
if (bitmap != null) {
//缓存中存在该图片的话就设置给ImageView
ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));
if (mImageView != null) {
mImageView.setImageBitmap(bitmap);
}
} else {
//不存在的话就开启一个异步线程去下载
DownloadTask task = new DownloadTask();
mDownloadTaskList.add(task);//把下载任务加入至下载集合中
task.execute(url);
}
}
} class DownloadTask extends AsyncTask<String, Void, Bitmap> {
String url;
@Override
protected Bitmap doInBackground(String... params) {
//在后台開始下载图片
url = params[0];
Bitmap bitmap = downloadBitmap(url);
if (bitmap != null) {
//把下载好的图片放入LruCache中
String key = String2MD5Tools.hashKeyForDisk(url);
putBitmapToLruCache(key, bitmap);
}
return bitmap;
} @Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下载好的图片显示出来
ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
mDownloadTaskList.remove(this);//把下载好的任务移除
}
} } /**
* @param tasks
* 取消全部的下载任务
*/
public void cancelAllDownloadTask(){
if(mDownloadTaskList!=null){
for (int i = 0; i < mDownloadTaskList.size(); i++) {
mDownloadTaskList.get(i).cancel(true);
}
}
}
/**
* 建立网络链接下载图片
*
* @param urlStr
* @return
*/
private Bitmap downloadBitmap(String urlStr) {
HttpURLConnection connection = null;
Bitmap bitmap = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream mInputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(mInputStream);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return bitmap;
}
}

好了。LruCache就介绍到这了。当中上面的样例有个不足就是没有做图片大小检查,过大的图片没有压缩。。

下一篇来介绍下怎么对过大的图片进行压缩!!!

源代码地址:http://download.csdn.net/detail/u010687392/8920169

让App中增加LruCache缓存,轻松解决图片过多造成的OOM的更多相关文章

  1. 让App中加入LruCache缓存,轻松解决图片过多造成的OOM

    上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,现在好好理一下吧. Android中一般情况下采取的缓存策略是使用二级缓存,即内存缓存+硬盘缓存->LruCache+Dis ...

  2. 使用memcached缓存 替代solr中的LRUCache缓存

    前沿 在搜索引擎中,缓存被当做是不可缺少的部分,但是很多情况下,将缓存的实现过度依赖于分发服务器及webserver会很大程度上加重webserver 的负担,具体表现就是经常性的假死,拒绝服务,因此 ...

  3. 在iOS App中增加完整的照片多选功能

    转自:http://blog.csdn.net/jasonblog/article/details/8141850 主要参考了ELCImagePickerController,不过由于UI展现上需要定 ...

  4. android 使用LruCache缓存网络图片

    加载图片,图片如果达到一定的上限,如果没有一种合理的机制对图片进行释放必然会引起程序的崩溃. 为了避免这种情况,我们可以使用Android中LruCache来缓存下载的图片,防止程序出现OOM.   ...

  5. android应用中增加权限判断

    android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ...

  6. Cordova for android怎样在App中处理退出button事件

    项目须要在HTML5 Android App中增加对返回键的处理,发现直接在Activity中加返回键处理代码不起作用,分析cordova源代码发现返回键已经被WebView处理掉了,所以仅仅能在js ...

  7. Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能。

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能. 下面是一个效果图 ...

  8. 一招解决微信小程序中的H5缓存问题

    一招解决微信小程序中的H5缓存问题1.问题描述开发过程中,为了更新代码方便,往往会在小程序中嵌入H5页面.但问题来了,小程序原生代码更新版本后,简单的从微信中删除或者代码强刷就可以解决缓存问题,但小程 ...

  9. 解决“iOS 7 app自动更新,无法在app中向用户展示更新内容”问题

    转自cocoachina iOS 7能在后台自动app,这对开发者来说和用户都很方便,但是还是有一些缺点.用户不会知道app本次更新的内容,除非他们上到app的App Store页面去查看.开发者也会 ...

随机推荐

  1. 有向图强连通分量的Tarjan算法和Kosaraju算法

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

  2. git指南目录

    git指南目录 发表回复 蓝色表示未阅读,棕色表示阅读过,绿色表示阅读过但不太理解 1. 起步 1.1 关于版本控制 1.2 Git 简史 1.3 Git 基础 1.4 安装 Git 1.5 初次运行 ...

  3. 什么是NAS

    个人理解: 1.NAS本身不是一种传输协议,只是一个名词而已,就是一个网络储存. 2.NAS系统本身就是一个Linux,也不是什么发行版,就是在Linux下实现了网络储存. 3.NAS系统里面实现了很 ...

  4. 使用CefSharp在.Net程序中嵌入Chrome浏览器(十)——独立文件夹部署

    CefSharp本身携带了一大堆文件,这些文件默认直接释放在exe文件底下,这种方式本身没有什么问题,但多了一大堆文件后不是很好看.本文这里就介绍一个方法,使得可以将CEF相关的文件部署到独立的文件夹 ...

  5. USBDM Debugger interface for Freescale RS08,HCS08,HCS12,Coldfire and ARM-Kinetis Devices.

    Introduction USBDM is a debugger hardware interface for a range of Freescale microcontrollers. It is ...

  6. WebClient 通过get和post请求api

    //get 请求        string url = string.Format("http://localhost:28450/api/values?str1=a&str2=b ...

  7. SpringMVC介绍之视图解析器ViewResolver

    在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视图解析器.当我们对SpringMVC控制的资源发起请求时,这些请求都会被SpringMV ...

  8. Cocos2d-x3.1TestCpp之NewRenderTest Demo分析

    1.代码构成 VisibleRect.h VisibleRect.cpp AppDelegate.h AppDelegate.cpp HelloWorldScene.h HelloWorldScene ...

  9. SAP BAPI一览 史上最全

    全BADI一览  List of BAPI's       BAPI WG Component Function module name Description Description Obj. Ty ...

  10. ASP.NET Web API实践系列09,在Fiddler和控制台中模拟GET和POST请求

    ASP.NET Web API本质是由一个进程托管的一组类,需要宿主,这个宿主可以是ASP.NET应用程序,可以是MVC项目,可以是控制台应用程序,也可以是自己定制的宿主. 在VS2012中创建一个& ...