上次有过电话面试中问到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 - 丢失的次数

再看看构造器:

  1. public LruCache(int maxSize) {
  2. if (maxSize <= 0) {
  3. throw new IllegalArgumentException("maxSize <= 0");
  4. }
  5. this.maxSize = maxSize;
  6. this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
  7. }

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

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

put()方法:

  1. public final V put(K key, V value) {
  2. if (key == null || value == null) {
  3. throw new NullPointerException("key == null || value == null");
  4. }
  5.  
  6. V previous;
  7. synchronized (this) {
  8. putCount++;
  9. size += safeSizeOf(key, value);
  10. previous = map.put(key, value);
  11. if (previous != null) {
  12. size -= safeSizeOf(key, previous);
  13. }
  14. }
  15.  
  16. if (previous != null) {
  17. entryRemoved(false, key, previous, value);
  18. }
  19.  
  20. trimToSize(maxSize);
  21. return previous;
  22. }

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

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

get()方法:

  1. public final V get(K key) {
  2. if (key == null) {
  3. throw new NullPointerException("key == null");
  4. }
  5.  
  6. V mapValue;
  7. synchronized (this) {
  8. mapValue = map.get(key);
  9. if (mapValue != null) {
  10. hitCount++;
  11. return mapValue;
  12. }
  13. missCount++;
  14. }
  15.  
  16. /*
  17. * Attempt to create a value. This may take a long time, and the map
  18. * may be different when create() returns. If a conflicting value was
  19. * added to the map while create() was working, we leave that value in
  20. * the map and release the created value.
  21. */
  22.  
  23. V createdValue = create(key);
  24. if (createdValue == null) {
  25. return null;
  26. }
  27.  
  28. synchronized (this) {
  29. createCount++;
  30. mapValue = map.put(key, createdValue);
  31.  
  32. if (mapValue != null) {
  33. // There was a conflict so undo that last put
  34. map.put(key, mapValue);
  35. } else {
  36. size += safeSizeOf(key, createdValue);
  37. }
  38. }
  39.  
  40. if (mapValue != null) {
  41. entryRemoved(false, key, createdValue, mapValue);
  42. return mapValue;
  43. } else {
  44. trimToSize(maxSize);
  45. return createdValue;
  46. }
  47. }

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

假如Value不存在则返回null。

remove()方法:

  1. public final V remove(K key) {
  2. if (key == null) {
  3. throw new NullPointerException("key == null");
  4. }
  5.  
  6. V previous;
  7. synchronized (this) {
  8. previous = map.remove(key);
  9. if (previous != null) {
  10. size -= safeSizeOf(key, previous);
  11. }
  12. }
  13.  
  14. if (previous != null) {
  15. entryRemoved(false, key, previous, null);
  16. }
  17.  
  18. return previous;
  19. }

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

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

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

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

一般我们会这样重写:

  1. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  2. @Override
  3. protected int sizeOf(String key, Bitmap bitmap) {
  4. if(bitmap!=null){
  5. return bitmap.getByteCount();
  6. }
  7. return 0;
  8. }
  9. };

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

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

  1. public class MyLruCache extends AppCompatActivity{
  2. private LruCache<String,Bitmap> mLruCache;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. //得到应用程序最大可用内存
  7. int maxCache = (int) Runtime.getRuntime().maxMemory();
  8. int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8
  9. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  10. @Override
  11. protected int sizeOf(String key, Bitmap bitmap) {
  12. if(bitmap!=null){
  13. return bitmap.getByteCount();
  14. }
  15. return 0;
  16. }
  17. };
  18. }
  19. /**
  20. * 加入Bitmap到LruCache中
  21. *
  22. * @param key
  23. * @param bitmap
  24. */
  25. public void putBitmapToLruCache(String key, Bitmap bitmap) {
  26. if (getBitmapFromLruCache(key) == null) {
  27. mLruCache.put(key, bitmap);
  28. }
  29. }
  30.  
  31. /**
  32. * @param key
  33. * @return 从LruCache缓存中获取一张Bitmap。没有则会返回null
  34. */
  35. public Bitmap getBitmapFromLruCache(String key) {
  36. return mLruCache.get(key);
  37. }
  38. }

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

先看看效果吧:

贴下主要代码:

MainActivity:

  1. public class MainActivity extends ActionBarActivity {
  2. private GridView mGridView;
  3. private List<String> datas;
  4. private Toolbar mToolbar;
  5. private GridViewAdapter mAdapter;
  6.  
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_main);
  11. Log.v("zxy", "cache:" + getCacheDir().getPath());
  12. Log.v("zxy", "Excache:" + getExternalCacheDir().getPath());
  13. mToolbar = (Toolbar) findViewById(R.id.toolbar);
  14. mToolbar.setTitleTextColor(Color.WHITE);
  15. mToolbar.setNavigationIcon(R.mipmap.icon);
  16. setSupportActionBar(mToolbar);
  17. initDatas();
  18.  
  19. mGridView = (GridView) findViewById(R.id.gridView);
  20. mAdapter = new GridViewAdapter(this, mGridView, datas);
  21. mGridView.setAdapter(mAdapter);
  22. mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  23. @Override
  24. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  25. Toast.makeText(MainActivity.this, "position=" + position + ",id=" + id, Toast.LENGTH_SHORT).show();
  26. }
  27. });
  28.  
  29. }
  30.  
  31. public void initDatas() {
  32. datas = new ArrayList<>();
  33. for (int i = 0; i < 55; i++) {
  34. datas.add(URLDatasTools.imageUrls[i]);
  35. }
  36. }
  37.  
  38. @Override
  39. protected void onDestroy() {
  40. super.onDestroy();
  41. mAdapter.cancelAllDownloadTask();//取消全部下载任务
  42. }
  43. }

GridViewAdapter:

  1. public class GridViewAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
  2. private List<DownloadTask> mDownloadTaskList;//全部下载异步线程的集合
  3. private Context mContext;
  4. private GridView mGridView;
  5. private List<String> datas;
  6. private LruCache<String, Bitmap> mLruCache;
  7. private int mFirstVisibleItem;//当前页显示的第一个item的位置position
  8. private int mVisibleItemCount;//当前页共显示了多少个item
  9. private boolean isFirstRunning = true;
  10.  
  11. public GridViewAdapter(Context context, GridView mGridView, List<String> datas) {
  12. this.mContext = context;
  13. this.datas = datas;
  14. this.mGridView = mGridView;
  15. this.mGridView.setOnScrollListener(this);
  16. mDownloadTaskList = new ArrayList<>();
  17. initCache();
  18. }
  19.  
  20. private void initCache() {
  21. //得到应用程序最大可用内存
  22. int maxCache = (int) Runtime.getRuntime().maxMemory();
  23. int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8
  24. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  25. @Override
  26. protected int sizeOf(String key, Bitmap bitmap) {
  27. if (bitmap != null) {
  28. return bitmap.getByteCount();
  29. }
  30. return 0;
  31. }
  32. };
  33. }
  34.  
  35. @Override
  36. public int getCount() {
  37. return datas.size();
  38. }
  39.  
  40. @Override
  41. public Object getItem(int position) {
  42. return datas.get(position);
  43. }
  44.  
  45. @Override
  46. public long getItemId(int position) {
  47. return position;
  48. }
  49.  
  50. @Override
  51. public View getView(int position, View convertView, ViewGroup parent) {
  52. convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_item, parent, false);
  53. ImageView mImageView = (ImageView) convertView.findViewById(R.id.imageView);
  54. TextView mTextView = (TextView) convertView.findViewById(R.id.textView);
  55. String url = datas.get(position);
  56. mImageView.setTag(String2MD5Tools.hashKeyForDisk(url));//设置一个Tag为md5(url),保证图片不错乱显示
  57. mTextView.setText("第" + position + "项");
  58. setImageViewForBitmap(mImageView, url);
  59. return convertView;
  60.  
  61. }
  62.  
  63. /**
  64. * 给ImageView设置Bitmap
  65. *
  66. * @param imageView
  67. * @param url
  68. */
  69. private void setImageViewForBitmap(ImageView imageView, String url) {
  70. String key = String2MD5Tools.hashKeyForDisk(url);//对url进行md5编码
  71. Bitmap bitmap = getBitmapFromLruCache(key);
  72. if (bitmap != null) {
  73. //假设缓存中存在。那么就设置缓存中的bitmap
  74. imageView.setImageBitmap(bitmap);
  75. } else {
  76. //不存在就设置个默认的背景色
  77. imageView.setBackgroundResource(R.color.color_five);
  78. }
  79. }
  80.  
  81. /**
  82. * 加入Bitmap到LruCache中
  83. *
  84. * @param key
  85. * @param bitmap
  86. */
  87. public void putBitmapToLruCache(String key, Bitmap bitmap) {
  88. if (getBitmapFromLruCache(key) == null) {
  89. mLruCache.put(key, bitmap);
  90. }
  91. }
  92.  
  93. /**
  94. * @param key
  95. * @return 从LruCache缓存中获取一张Bitmap,没有则会返回null
  96. */
  97. public Bitmap getBitmapFromLruCache(String key) {
  98. return mLruCache.get(key);
  99. }
  100.  
  101. @Override
  102. public void onScrollStateChanged(AbsListView view, int scrollState) {
  103. if (scrollState == SCROLL_STATE_IDLE) {//GridView为精巧状态时,让它去下载图片
  104. loadBitmap(mFirstVisibleItem, mVisibleItemCount);
  105. } else {
  106. //滚动时候取消全部下载任务
  107. cancelAllDownloadTask();
  108. }
  109. }
  110.  
  111. @Override
  112. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  113. mFirstVisibleItem = firstVisibleItem;
  114. mVisibleItemCount = visibleItemCount;
  115. if (isFirstRunning && visibleItemCount > 0) {//首次进入时载入图片
  116. loadBitmap(mFirstVisibleItem, mVisibleItemCount);
  117. isFirstRunning = false;
  118. }
  119. }
  120.  
  121. /**
  122. * 载入图片到ImageView中
  123. *
  124. * @param mFirstVisibleItem
  125. * @param mVisibleItemCount
  126. */
  127. private void loadBitmap(int mFirstVisibleItem, int mVisibleItemCount) {
  128. //首先推断图片在不在缓存中,假设不在就开启异步线程去下载该图片
  129. for (int i = mFirstVisibleItem; i < mFirstVisibleItem + mVisibleItemCount; i++) {
  130. final String url = datas.get(i);
  131. String key = String2MD5Tools.hashKeyForDisk(url);
  132. Bitmap bitmap = getBitmapFromLruCache(key);
  133. if (bitmap != null) {
  134. //缓存中存在该图片的话就设置给ImageView
  135. ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));
  136. if (mImageView != null) {
  137. mImageView.setImageBitmap(bitmap);
  138. }
  139. } else {
  140. //不存在的话就开启一个异步线程去下载
  141. DownloadTask task = new DownloadTask();
  142. mDownloadTaskList.add(task);//把下载任务加入至下载集合中
  143. task.execute(url);
  144. }
  145. }
  146. }
  147.  
  148. class DownloadTask extends AsyncTask<String, Void, Bitmap> {
  149. String url;
  150. @Override
  151. protected Bitmap doInBackground(String... params) {
  152. //在后台開始下载图片
  153. url = params[0];
  154. Bitmap bitmap = downloadBitmap(url);
  155. if (bitmap != null) {
  156. //把下载好的图片放入LruCache中
  157. String key = String2MD5Tools.hashKeyForDisk(url);
  158. putBitmapToLruCache(key, bitmap);
  159. }
  160. return bitmap;
  161. }
  162.  
  163. @Override
  164. protected void onPostExecute(Bitmap bitmap) {
  165. super.onPostExecute(bitmap);
  166. //把下载好的图片显示出来
  167. ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));
  168. if (mImageView != null && bitmap != null) {
  169. mImageView.setImageBitmap(bitmap);
  170. mDownloadTaskList.remove(this);//把下载好的任务移除
  171. }
  172. }
  173.  
  174. }
  175.  
  176. /**
  177. * @param tasks
  178. * 取消全部的下载任务
  179. */
  180. public void cancelAllDownloadTask(){
  181. if(mDownloadTaskList!=null){
  182. for (int i = 0; i < mDownloadTaskList.size(); i++) {
  183. mDownloadTaskList.get(i).cancel(true);
  184. }
  185. }
  186. }
  187. /**
  188. * 建立网络链接下载图片
  189. *
  190. * @param urlStr
  191. * @return
  192. */
  193. private Bitmap downloadBitmap(String urlStr) {
  194. HttpURLConnection connection = null;
  195. Bitmap bitmap = null;
  196. try {
  197. URL url = new URL(urlStr);
  198. connection = (HttpURLConnection) url.openConnection();
  199. connection.setConnectTimeout(5000);
  200. connection.setReadTimeout(5000);
  201. connection.setDoInput(true);
  202. connection.connect();
  203. if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
  204. InputStream mInputStream = connection.getInputStream();
  205. bitmap = BitmapFactory.decodeStream(mInputStream);
  206. }
  207. } catch (MalformedURLException e) {
  208. e.printStackTrace();
  209. } catch (IOException e) {
  210. e.printStackTrace();
  211. } finally {
  212. if (connection != null) {
  213. connection.disconnect();
  214. }
  215. }
  216. return bitmap;
  217. }
  218. }

好了。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. BZOJ4045 : [Cerc2014] bricks

    首先求出B和W的个数,如果只出现了一种那么直接输出sum(k). 否则依次扫描,能割就割,时间复杂度$O(n)$. #include<cstdio> #define N 100010 ty ...

  2. BZOJ4275 : [ONTAK2015]Badania naukowe

    设f[i][j]为a[1..i]与b[1..j]的LCS,g[i][j]为a[i..n]与b[j..m]的LCS. 若C为0,则ans=f[n][m]. 否则求出d[i]=a中从i开始往右匹配上c串的 ...

  3. Codeforces Round #374 (Div. 2) A. One-dimensional Japanese Crosswor 水题

    A. One-dimensional Japanese Crossword 题目连接: http://codeforces.com/contest/721/problem/A Description ...

  4. Spring_错误 java.sql.SQLException: Lock wait timeout exceeded | CannotAcquireLockException 的解决

    java.sql.SQLException: Lock wait timeout exceeded |  org.springframework.dao.CannotAcquireLockExcept ...

  5. CentOS 7下启动postfix服务报错:fatal: parameter inet_interfaces: no local interface found for ::1

    sed -i 's/inet_interfaces = localhost/inet_interfaces = all' /etc/postfix/main.cf service postfix re ...

  6. JAVA泛型中的有界类型(extends super)(转)

    JDK1.5中引入了泛型(Generic)机制.泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Ja ...

  7. Sublime Text 2 快捷键(转)

    文件 File 新建文件 Ctrl + N 打开文件 Ctrl + O 打开最近关闭的文件 Ctrl + Shift + T 保存 Ctrl + S 另存为… Ctrl + Shift + S 关闭文 ...

  8. 阻止新的csproj工程的dll引用继承

    VisualStudio传统的csproj工程中,引用是没有继承功能的.例如,对于如下一个引用关系 App引用Assembly 1 Assembly 1引用Assembly 2 程序App在没有添加A ...

  9. STM32输入捕获模式设置并用DMA接收数据

    参考: STM32的PWM输入模式设置并用DMA接收数据 Input capture mode The input stage samples the corresponding TIx input ...

  10. USBDM RS08/HCS08/HCS12/Coldfire V1,2,3,4/DSC/Kinetis Debugger and Programmer -- BDM Construction and Firmware

    Construction. Build the hardware using the information provided in the PCB download. The following a ...