现在做的项目中,有用到一个开源的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,否则重用可能会失败。

  具体使用:  

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
bitmapOptions.inBitmap = obtainSizedBitmapFromPool();
bitmapOptions.inSampleSize = 1;
bitmapOptions.inMutable = true;
} Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream, null, bitmapOptions);
Drawable drawable = new ReusableBitmapDrawable(bitmap);

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

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

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

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

  代码:

public class ReusableBitmapDrawable extends BitmapDrawable {

    private boolean mBitmapRecycled = false;
private int mUsageRefCount = 0; public ReusableBitmapDrawable(Bitmap pBitmap) {
super(pBitmap);
} /**
* 开始使用图片,引用数+1,只要有地方引用图片就不能回收
* 之后还是需要通过isBitmapValid()确保图片有效能用
*/
public void beginUsingDrawable() {
synchronized (this) {
mUsageRefCount++;
}
} /**
* 某个地方使用图片结束,引用数-1
*/
public void finishUsingDrawable() {
synchronized (this) {
mUsageRefCount--;
if (mUsageRefCount < 0)
throw new IllegalStateException("Unbalanced endUsingDrawable() called.");
}
} /**
* 如果没有任何引用了,返回图片以便recycle释放内存或者加入BitmapPool以供复用
*/
public Bitmap tryRecycle() {
synchronized (this) {
if (mUsageRefCount == 0) {
mBitmapRecycled = true;
return getBitmap();
}
}
return null;
} /**
* 判定是否已被回收或者加入缓存池
*/
public boolean isBitmapValid() {
synchronized (this) {
return !mBitmapRecycled;
}
}
}

  使用示例:

Drawable currentMapTile = ......

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

  

  当从内存中移除时:

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

  LruCache:

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;
private int createCount;
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);
}
/**
* Sets the size of the cache.
*
* @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
/**
* 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.
*/
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;
}
}
/**
* 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) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public 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 || map.isEmpty()) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
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.
*
* @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.
*
* <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}.
* @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.
*
* <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.
*
* <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.
*/
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.
*/
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);
}
}

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

public class BitmapPool {
final LinkedList<Bitmap> mPool = new LinkedList<Bitmap>(); private static BitmapPool sInstance; public static BitmapPool getInstance() {
if (sInstance == null)
sInstance = new BitmapPool(); return sInstance;
} 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);
        }
      }

    }

public void applyReusableOptions(final BitmapFactory.Options aBitmapOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
aBitmapOptions.inBitmap = obtainBitmapFromPool();
aBitmapOptions.inSampleSize = 1;
aBitmapOptions.inMutable = true;
}
} public Bitmap obtainBitmapFromPool() {
synchronized (mPool) {
if (mPool.isEmpty()) {
return null;
} else {
final Bitmap bitmap = mPool.removeFirst();
if (bitmap.isRecycled()) {
return obtainBitmapFromPool(); // recurse
} else {
return bitmap;
}
}
}
} public Bitmap obtainSizedBitmapFromPool(final int aWidth, final int aHeight) {
synchronized (mPool) {
if (mPool.isEmpty()) {
return null;
} else {
for (final Bitmap bitmap : mPool) {
if (bitmap.isRecycled()) {
mPool.remove(bitmap);
return obtainSizedBitmapFromPool(aWidth, aHeight); // recurse to prevent ConcurrentModificationException
} else if (bitmap.getWidth() == aWidth && bitmap.getHeight() == aHeight) {
mPool.remove(bitmap);
return bitmap;
}
}
}
} return null;
} public void clearBitmapPool() {
synchronized (sInstance.mPool) {
while (!sInstance.mPool.isEmpty()) {
Bitmap bitmap = sInstance.mPool.remove();
bitmap.recycle();
}
}
}
}

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

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-UIButton-文字位置,字体大小,边角样式,button种类,点击事件,内容位置

    一. 设置button的文字居左,居中,居右 //设置button居左 button.contentHorizontalAlignment = UIControlContentHorizontalAl ...

  2. Android 学习笔记多媒体技术之 AsyncTask+实现音频播放...

    PS:今天搞了一下如何实现音频播放...结果被坑了,看书上写的代码是挺简单的,但是有个函数就是死活没看懂,这真是受不了...最后才弄明白,原来是一个实现异步任务的一个类...这个类使用java.uti ...

  3. HTML简明教程(一)

    HTML简明教程(一) 内容主体来自:W3School 一.HTML 简介 二.HTML 基础 三.HTML 元素 四.HTML 属性 五.HTML 标题 六.HTML 段落 七.HTML 文本格式化 ...

  4. 一起Polyfill系列:Function.prototype.bind的四个阶段

    昨天边参考es5-shim边自己实现Function.prototype.bind,发现有不少以前忽视了的地方,这里就作为一个小总结吧. 一.Function.prototype.bind的作用 其实 ...

  5. SQL Server 文件和文件组

    文件和文件组简介 在SQL Server中,数据库在硬盘上的存储方式和普通文件在Windows中的存储方式没有什么不同,仅仅是几个文件而已.SQL Server通过管理逻辑上的文件组的方式来管理文件. ...

  6. python中的__init__ 、__new__、__call__小结

    这篇文章主要介绍了python中的__init__ .__new__.__call__小结,需要的朋友可以参考下 1.__new__(cls, *args, **kwargs)  创建对象时调用,返回 ...

  7. Titanium开发环境搭建第三个坑

    Nodejs版本的问题 首先,如下引用:http://docs.appcelerator.com/titanium/latest/#!/guide/Installing_Node To run all ...

  8. jquery 拓展

    1. 概述 jquery允许拓展自定义的方法, 绑定到$.fn对象上, 编写一个jQuery插件的原则: 给$.fn绑定函数,实现插件的代码逻辑: 插件函数最后要return this;以支持链式调用 ...

  9. (旧)子数涵数·PS——换脸

    一.打开Photoshop,打开下载好的两张素材图(均在百度下载的). 二.选中人物脸的部分,不需要很精确(哪种抠图方式都行,我用的是矩形选框工具),再使用移动工具(快捷键为V,或者按着Ctrl),将 ...

  10. R语言-神经网络包RSNNS

    code{white-space: pre;} pre:not([class]) { background-color: white; }if (window.hljs && docu ...