现在做的项目中,有用到一个开源的2D地图框架osmdroid,但是在项目中,使用还是有一些问题,例如,多个地图实例,会有独自的图片缓存,Activity onPause时,并不会释放图片缓存,如果多级界面都有地图的话,可能会造成很多手机内存溢出(按照每个瓦片256*256,屏幕1280*720来算,显示一个屏幕的地图,至少要在内存保存15张图片,占用内存256*256*4*15=3.75M),所以还是得针对项目做一定的修改调整。今天将项目中使用到的图片缓存做一个整理。

  首先说下整体的图片加载流程:

  1、内存中加载。通过LruCache保存,LruCache中移除的图片,如果没有引用使用,会缓存到BitmapPool以供重用,避免频繁申请释放内存,这样滑动时加载图片会更平滑;

  2、从文件加载。内存中没有,会从瓦片缓存目录中加载,加载成功保存到内存并通知地图刷新显示;

  3、从网络加载。如果本地文件也没有,会从网络下载,然后保存到瓦片缓存目录,以及内存中,然后再通知地图刷新显示。

  接下来说一下Bitmap相关的几个重要的参数:

  BitmapFactory.Options.inBitmap:

  If set, decode methods that take the Options object will attempt to reuse this bitmap when loading content. If the decode operation cannot use this bitmap, the decode method will return null and will throw an IllegalArgumentException. The current implementation necessitates that the reused bitmap be of the same size as the source content and in jpeg or png format (whether as a resource or as a stream). The configuration of the reused bitmap will override the setting of inPreferredConfig, if set.

  You should still always use the returned Bitmap of the decode method and not assume that reusing the bitmap worked, due to the constraints outlined above and failure situations that can occur. Checking whether the return value matches the value of the inBitmap set in the Options structure is a way to see if the bitmap was reused, but in all cases you should use the returned Bitmap to make sure that you are using the bitmap that was used as the decode destination.

  从Android 3.0(API level 11)开始,引入了BitmapFactory.Options.inBitmap字段,如果这个属性被设置了,拥有这个Options对象的方法在解析图片的时候会尝试复用一张已存在的图片。这意味着图片缓存被复用了,这意味着更流畅的用户体验以及更好的内存分配和回收。然而,要使用inBitmap有这一定的限制:

  1、在Android 4.4(API level 19)之前,只有相同大小的Btiamp才会被复用;

  2、必须配合inMutable=true,inSampleSize=1一起使用;

  3、一定要使用解码方法BitmapFactory.decodeStream返回的Bitmap,否则重用可能会失败。

  具体使用:  

  1. BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
  2.  
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  4. bitmapOptions.inBitmap = obtainSizedBitmapFromPool();
  5. bitmapOptions.inSampleSize = 1;
  6. bitmapOptions.inMutable = true;
  7. }
  8.  
  9. Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream, null, bitmapOptions);
    Drawable drawable = new ReusableBitmapDrawable(bitmap);

  如果是在Android 2.3.3 (API level 10),以及更低的版本中,推荐使用Bitmap.recycle方法加快Bitmap内存的回收。

  1. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
  2. //BitmapOptions.inBitmap无效,不缓存图片
  3. if (b != null && !b.isRecycled()){
  4. b.recycle();
  5. }
  6. return;
  7. }

  然后介绍下使用到的几个比较重要的类:

  ReusableBitmapDrawable:主要是记录Bitmap的引用数,在从内存中移除时判断是否可以recycle回收释放内存或添加到BitmapPool,也可避免"trying to use a recycled bitmap"得异常。

  代码:

  1. public class ReusableBitmapDrawable extends BitmapDrawable {
  2.  
  3. private boolean mBitmapRecycled = false;
  4. private int mUsageRefCount = 0;
  5.  
  6. public ReusableBitmapDrawable(Bitmap pBitmap) {
  7. super(pBitmap);
  8. }
  9.  
  10. /**
  11. * 开始使用图片,引用数+1,只要有地方引用图片就不能回收
  12. * 之后还是需要通过isBitmapValid()确保图片有效能用
  13. */
  14. public void beginUsingDrawable() {
  15. synchronized (this) {
  16. mUsageRefCount++;
  17. }
  18. }
  19.  
  20. /**
  21. * 某个地方使用图片结束,引用数-1
  22. */
  23. public void finishUsingDrawable() {
  24. synchronized (this) {
  25. mUsageRefCount--;
  26. if (mUsageRefCount < 0)
  27. throw new IllegalStateException("Unbalanced endUsingDrawable() called.");
  28. }
  29. }
  30.  
  31. /**
  32. * 如果没有任何引用了,返回图片以便recycle释放内存或者加入BitmapPool以供复用
  33. */
  34. public Bitmap tryRecycle() {
  35. synchronized (this) {
  36. if (mUsageRefCount == 0) {
  37. mBitmapRecycled = true;
  38. return getBitmap();
  39. }
  40. }
  41. return null;
  42. }
  43.  
  44. /**
  45. * 判定是否已被回收或者加入缓存池
  46. */
  47. public boolean isBitmapValid() {
  48. synchronized (this) {
  49. return !mBitmapRecycled;
  50. }
  51. }
  52. }

  使用示例:

  1. Drawable currentMapTile = ......
  2.  
  3. boolean isReusable = currentMapTile != null
  4. && currentMapTile instanceof ReusableBitmapDrawable;
  5. final ReusableBitmapDrawable reusableBitmapDrawable =
  6. isReusable ? (ReusableBitmapDrawable) currentMapTile : null;
  1. if (isReusable) {
    reusableBitmapDrawable.beginUsingDrawable();
    }
    try {
    if (isReusable && !((ReusableBitmapDrawable) currentMapTile).isBitmapValid()) {
    currentMapTile = getLoadingTile(); //已经回收或者缓存,显示默认的加载图片
    isReusable = false;
    }
    onTileReadyToDraw(pCanvas, currentMapTile, mTileRect); //绘制瓦片
  2.  
  3. } finally {
    if (isReusable)
    reusableBitmapDrawable.finishUsingDrawable();
    }

  

  当从内存中移除时:

  1. public void returnDrawableToPool(ReusableBitmapDrawable drawable) {
  2. Bitmap b = drawable.tryRecycle();
  3.  
  4. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
  5. //BitmapOptions.inBitmap无效,不缓存图片
  6. if (b != null && !b.isRecycled()){
  7. b.recycle();
  8. }
  9. return;
  10. }
  11.  
  12. if (b != null && b.isMutable()){
  13. synchronized (mPool) {
  14. mPool.addLast(b);
  15. }
  16. }
  17. }

  LruCache:

  1. public class LruCache<K, V> {
  2. private final LinkedHashMap<K, V> map;
  3. /** Size of this cache in units. Not necessarily the number of elements. */
  4. private int size;
  5. private int maxSize;
  6. private int putCount;
  7. private int createCount;
  8. private int evictionCount;
  9. private int hitCount;
  10. private int missCount;
  11. /**
  12. * @param maxSize for caches that do not override {@link #sizeOf}, this is
  13. * the maximum number of entries in the cache. For all other caches,
  14. * this is the maximum sum of the sizes of the entries in this cache.
  15. */
  16. public LruCache(int maxSize) {
  17. if (maxSize <= 0) {
  18. throw new IllegalArgumentException("maxSize <= 0");
  19. }
  20. this.maxSize = maxSize;
  21. this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
  22. }
  23. /**
  24. * Sets the size of the cache.
  25. *
  26. * @param maxSize The new maximum size.
  27. */
  28. public void resize(int maxSize) {
  29. if (maxSize <= 0) {
  30. throw new IllegalArgumentException("maxSize <= 0");
  31. }
  32. synchronized (this) {
  33. this.maxSize = maxSize;
  34. }
  35. trimToSize(maxSize);
  36. }
  37. /**
  38. * Returns the value for {@code key} if it exists in the cache or can be
  39. * created by {@code #create}. If a value was returned, it is moved to the
  40. * head of the queue. This returns null if a value is not cached and cannot
  41. * be created.
  42. */
  43. public final V get(K key) {
  44. if (key == null) {
  45. throw new NullPointerException("key == null");
  46. }
  47. V mapValue;
  48. synchronized (this) {
  49. mapValue = map.get(key);
  50. if (mapValue != null) {
  51. hitCount++;
  52. return mapValue;
  53. }
  54. missCount++;
  55. }
  56. /*
  57. * Attempt to create a value. This may take a long time, and the map
  58. * may be different when create() returns. If a conflicting value was
  59. * added to the map while create() was working, we leave that value in
  60. * the map and release the created value.
  61. */
  62. V createdValue = create(key);
  63. if (createdValue == null) {
  64. return null;
  65. }
  66. synchronized (this) {
  67. createCount++;
  68. mapValue = map.put(key, createdValue);
  69. if (mapValue != null) {
  70. // There was a conflict so undo that last put
  71. map.put(key, mapValue);
  72. } else {
  73. size += safeSizeOf(key, createdValue);
  74. }
  75. }
  76. if (mapValue != null) {
  77. entryRemoved(false, key, createdValue, mapValue);
  78. return mapValue;
  79. } else {
  80. trimToSize(maxSize);
  81. return createdValue;
  82. }
  83. }
  84. /**
  85. * Caches {@code value} for {@code key}. The value is moved to the head of
  86. * the queue.
  87. *
  88. * @return the previous value mapped by {@code key}.
  89. */
  90. public final V put(K key, V value) {
  91. if (key == null || value == null) {
  92. throw new NullPointerException("key == null || value == null");
  93. }
  94. V previous;
  95. synchronized (this) {
  96. putCount++;
  97. size += safeSizeOf(key, value);
  98. previous = map.put(key, value);
  99. if (previous != null) {
  100. size -= safeSizeOf(key, previous);
  101. }
  102. }
  103. if (previous != null) {
  104. entryRemoved(false, key, previous, value);
  105. }
  106. trimToSize(maxSize);
  107. return previous;
  108. }
  109. /**
  110. * Remove the eldest entries until the total of remaining entries is at or
  111. * below the requested size.
  112. *
  113. * @param maxSize the maximum size of the cache before returning. May be -1
  114. * to evict even 0-sized elements.
  115. */
  116. public void trimToSize(int maxSize) {
  117. while (true) {
  118. K key;
  119. V value;
  120. synchronized (this) {
  121. if (size < 0 || (map.isEmpty() && size != 0)) {
  122. throw new IllegalStateException(getClass().getName()
  123. + ".sizeOf() is reporting inconsistent results!");
  124. }
  125. if (size <= maxSize || map.isEmpty()) {
  126. break;
  127. }
  128. Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
  129. key = toEvict.getKey();
  130. value = toEvict.getValue();
  131. map.remove(key);
  132. size -= safeSizeOf(key, value);
  133. evictionCount++;
  134. }
  135. entryRemoved(true, key, value, null);
  136. }
  137. }
  138. /**
  139. * Removes the entry for {@code key} if it exists.
  140. *
  141. * @return the previous value mapped by {@code key}.
  142. */
  143. public final V remove(K key) {
  144. if (key == null) {
  145. throw new NullPointerException("key == null");
  146. }
  147. V previous;
  148. synchronized (this) {
  149. previous = map.remove(key);
  150. if (previous != null) {
  151. size -= safeSizeOf(key, previous);
  152. }
  153. }
  154. if (previous != null) {
  155. entryRemoved(false, key, previous, null);
  156. }
  157. return previous;
  158. }
  159. /**
  160. * Called for entries that have been evicted or removed. This method is
  161. * invoked when a value is evicted to make space, removed by a call to
  162. * {@link #remove}, or replaced by a call to {@link #put}. The default
  163. * implementation does nothing.
  164. *
  165. * <p>The method is called without synchronization: other threads may
  166. * access the cache while this method is executing.
  167. *
  168. * @param evicted true if the entry is being removed to make space, false
  169. * if the removal was caused by a {@link #put} or {@link #remove}.
  170. * @param newValue the new value for {@code key}, if it exists. If non-null,
  171. * this removal was caused by a {@link #put}. Otherwise it was caused by
  172. * an eviction or a {@link #remove}.
  173. */
  174. protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
  175. /**
  176. * Called after a cache miss to compute a value for the corresponding key.
  177. * Returns the computed value or null if no value can be computed. The
  178. * default implementation returns null.
  179. *
  180. * <p>The method is called without synchronization: other threads may
  181. * access the cache while this method is executing.
  182. *
  183. * <p>If a value for {@code key} exists in the cache when this method
  184. * returns, the created value will be released with {@link #entryRemoved}
  185. * and discarded. This can occur when multiple threads request the same key
  186. * at the same time (causing multiple values to be created), or when one
  187. * thread calls {@link #put} while another is creating a value for the same
  188. * key.
  189. */
  190. protected V create(K key) {
  191. return null;
  192. }
  193. private int safeSizeOf(K key, V value) {
  194. int result = sizeOf(key, value);
  195. if (result < 0) {
  196. throw new IllegalStateException("Negative size: " + key + "=" + value);
  197. }
  198. return result;
  199. }
  200. /**
  201. * Returns the size of the entry for {@code key} and {@code value} in
  202. * user-defined units. The default implementation returns 1 so that size
  203. * is the number of entries and max size is the maximum number of entries.
  204. *
  205. * <p>An entry's size must not change while it is in the cache.
  206. */
  207. protected int sizeOf(K key, V value) {
  208. return 1;
  209. }
  210. /**
  211. * Clear the cache, calling {@link #entryRemoved} on each removed entry.
  212. */
  213. public final void evictAll() {
  214. trimToSize(-1); // -1 will evict 0-sized elements
  215. }
  216. /**
  217. * For caches that do not override {@link #sizeOf}, this returns the number
  218. * of entries in the cache. For all other caches, this returns the sum of
  219. * the sizes of the entries in this cache.
  220. */
  221. public synchronized final int size() {
  222. return size;
  223. }
  224. /**
  225. * For caches that do not override {@link #sizeOf}, this returns the maximum
  226. * number of entries in the cache. For all other caches, this returns the
  227. * maximum sum of the sizes of the entries in this cache.
  228. */
  229. public synchronized final int maxSize() {
  230. return maxSize;
  231. }
  232. /**
  233. * Returns the number of times {@link #get} returned a value that was
  234. * already present in the cache.
  235. */
  236. public synchronized final int hitCount() {
  237. return hitCount;
  238. }
  239. /**
  240. * Returns the number of times {@link #get} returned null or required a new
  241. * value to be created.
  242. */
  243. public synchronized final int missCount() {
  244. return missCount;
  245. }
  246. /**
  247. * Returns the number of times {@link #create(Object)} returned a value.
  248. */
  249. public synchronized final int createCount() {
  250. return createCount;
  251. }
  252. /**
  253. * Returns the number of times {@link #put} was called.
  254. */
  255. public synchronized final int putCount() {
  256. return putCount;
  257. }
  258. /**
  259. * Returns the number of values that have been evicted.
  260. */
  261. public synchronized final int evictionCount() {
  262. return evictionCount;
  263. }
  264. /**
  265. * Returns a copy of the current contents of the cache, ordered from least
  266. * recently accessed to most recently accessed.
  267. */
  268. public synchronized final Map<K, V> snapshot() {
  269. return new LinkedHashMap<K, V>(map);
  270. }
  271. @Override public synchronized final String toString() {
  272. int accesses = hitCount + missCount;
  273. int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
  274. return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
  275. maxSize, hitCount, missCount, hitPercent);
  276. }
  277. }

  BitmapPool: 缓存Bitmap以便重用,避免频繁申请回收内存,使图片加载更加平滑。

  1. public class BitmapPool {
  2. final LinkedList<Bitmap> mPool = new LinkedList<Bitmap>();
  3.  
  4. private static BitmapPool sInstance;
  5.  
  6. public static BitmapPool getInstance() {
  7. if (sInstance == null)
  8. sInstance = new BitmapPool();
  9.  
  10. return sInstance;
  11. }
  12.  
  13. public void returnDrawableToPool(ReusableBitmapDrawable drawable) {
         Bitmap b = drawable.tryRecycle();

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        //BitmapOptions.inBitmap无效,不缓存图片
        if (b != null && !b.isRecycled()){
          b.recycle();
        }
        return;
      }

      if (b != null && b.isMutable()){

        synchronized (mPool) {
          mPool.addLast(b);
        }
      }

    }

  1. public void applyReusableOptions(final BitmapFactory.Options aBitmapOptions) {
  2. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  3. aBitmapOptions.inBitmap = obtainBitmapFromPool();
  4. aBitmapOptions.inSampleSize = 1;
  5. aBitmapOptions.inMutable = true;
  6. }
  7. }
  8.  
  9. public Bitmap obtainBitmapFromPool() {
  10. synchronized (mPool) {
  11. if (mPool.isEmpty()) {
  12. return null;
  13. } else {
  14. final Bitmap bitmap = mPool.removeFirst();
  15. if (bitmap.isRecycled()) {
  16. return obtainBitmapFromPool(); // recurse
  17. } else {
  18. return bitmap;
  19. }
  20. }
  21. }
  22. }
  23.  
  24. public Bitmap obtainSizedBitmapFromPool(final int aWidth, final int aHeight) {
  25. synchronized (mPool) {
  26. if (mPool.isEmpty()) {
  27. return null;
  28. } else {
  29. for (final Bitmap bitmap : mPool) {
  30. if (bitmap.isRecycled()) {
  31. mPool.remove(bitmap);
  32. return obtainSizedBitmapFromPool(aWidth, aHeight); // recurse to prevent ConcurrentModificationException
  33. } else if (bitmap.getWidth() == aWidth && bitmap.getHeight() == aHeight) {
  34. mPool.remove(bitmap);
  35. return bitmap;
  36. }
  37. }
  38. }
  39. }
  40.  
  41. return null;
  42. }
  43.  
  44. public void clearBitmapPool() {
  45. synchronized (sInstance.mPool) {
  46. while (!sInstance.mPool.isEmpty()) {
  47. Bitmap bitmap = sInstance.mPool.remove();
  48. bitmap.recycle();
  49. }
  50. }
  51. }
  52. }

  如果文章中又写的有问题的地方,欢迎反馈。

android图片缓存(包含ReusableBitmapDrawable和BitmapPool)的更多相关文章

  1. Android图片缓存之Glide进阶

    前言: 前面学习了Glide的简单使用(Android图片缓存之初识Glide),今天来学习一下Glide稍微复杂一点的使用. 图片缓存相关博客地址: Android图片缓存之Bitmap详解 And ...

  2. Android图片缓存之初识Glide

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  3. 安卓高级 Android图片缓存之初识Glide

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  4. Android图片缓存之Lru算法

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发 ...

  5. Android图片缓存之Bitmap详解

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. 图片缓存相关博客地址: Android图片缓 ...

  6. Android图片缓存框架Glide

    Android图片缓存框架Glide Glide是Google提供的一个组件.它具有获取.解码和展示视频剧照.图片.动画等功能.它提供了灵活的API,帮助开发者将Glide应用在几乎任何网络协议栈中. ...

  7. Android图片缓存之Glide进阶(四)

    前言: 前面学习了Glide的简单使用(http://www.cnblogs.com/whoislcj/p/5558168.html),今天来学习一下Glide稍微复杂一点的使用. GlideModu ...

  8. Android图片缓存之初识Glide(三)

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  9. Android图片缓存之Bitmap详解(一)

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. Bitmap: Bitmap是Android ...

随机推荐

  1. iOS-UITextField-通知

    二.处理文本框与键盘之间的关系(当键盘弹出遮挡到文本框的时候进行调整) 原理: 首先要明白: 1,键盘隐藏的时候,键盘上边界紧贴屏幕最低端,键盘在屏幕正下方. 2:键盘弹起的时候,键盘下边界跟屏幕下边 ...

  2. SystemTap了解

    SystemTrap是监控和跟踪运行中的Linux内核操作的动态方法. http://www.ibm.com/developerworks/cn/linux/l-systemtap/ 使用System ...

  3. Qt之QAbstractItemView视图项拖拽(二)

    一.需求说明 上一篇文章Qt之QAbstractItemView视图项拖拽(一)讲述了实现QAbstractItemView视图项拖拽的一种方式,是基于QDrag实现的,这个类是qt自己封装好了的,所 ...

  4. EF错误记录

    纯属个人记录错误使用: 1.EntityType“area”未定义键.请为该 EntityType 定义键. 产生原因: 1.命名空间引用错误,可能命名重复导致引用错误 2.实体类无法识别主键或者未设 ...

  5. SQL Server添加MDW性能监控报表

    10.2 Data Collector与MDW Data Collection功能是SQL SERVER 2005版本提供的数据库监控报表的功能,通过定时地对数据库的语句运行情况,服务器各种资源的监控 ...

  6. ASP.NET MVC5--为数据库新增字段(涉及数据库迁移技术)

    Setting up Code First Migrations for Model Changes--为模型更改做数据库迁移. 1.打开资源管理器,在App_Data文件夹下,找到movies.md ...

  7. C# SortedList类概念和示例

    SortedList 类 [C#] 命名空间: System.Collections 表示键/值对的集合,这些键和值按键排序并可按照键和索引访问. SortedList 是 Hashtable 和 A ...

  8. 在uwp中复活常用的vb库函数

    这个博文是纯原创的,转载一定要说明作者是 Nukepayload2!! 在.Net Core 中,很多地方被精简了,有个重灾区就是vb语言库.从当初的囊括vb6库函数并且附带后期绑定到现在的几个函数加 ...

  9. Fluent Nhibernate and Stored Procedures

    sql:存储过程 DROP TABLE Department GO CREATE TABLE Department ( Id INT IDENTITY(1,1) PRIMARY KEY, DepNam ...

  10. mysql zip install

    1.Question Description: 1.1 version: mysql-5.7.11-64 1.2 form: zip file 1.3 >mysqld --install (su ...