现在做的项目中,有用到一个开源的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. 精品素材:WALK & RIDE 单页网站模板下载

    今天,很高兴能向大家分享一个响应式的,简约风格的 HTML5 单页网站模板.Walk & Ride 这款单页网站模板是现代风格的网页模板,简洁干净,像素完美,特别适合用于推广移动 APP 应用 ...

  2. Android学习笔记之ConnectivityManager+NetWorkInfo

    PS:眼看就要开学了,该收收心了. 学习内容: 1.ConnecivityManager 2.NetWorkInfo   ConnectivityManger:网络连接管理者,用于管理Android设 ...

  3. 在Kibana上格式化字段,更好的在dashboard上展示

    一.为什么要格式化? 接着之前的文章-利用 ELK系统分析Nginx日志并对数据进行可视化展示.下面是http访问的日志,里面有一个字段,bytes 传输的字节,如下图: 绿色框框内选中的就是本次请求 ...

  4. Entity Framework的默认值BUG解决方法

    在使用.Net 3.5里的Entity Framework开发网站的时候,遇到了一个问题:添加记录时,对于DateTime型的数据,无法使用数据库的默认值. 具体的情况是这样的,我的数据库有个User ...

  5. 2013/11/21工作随笔-PHP开启多进程

    今天被问到一个问题,php如何开启多进程才比较稳定. php开启多进程执行一个操作有哪些方法: 首先想到的是使用pcntl的fork 具体可以参考之前的文章:PHP的pcntl多进程 其次想到的方法是 ...

  6. C#的pictureBox怎样使用多张图片简单切换

    首先,先创建一个新的winform项目ImageTest,选择窗体,起名我ImageForm,在ImageForm拉一个picturebox控件,一个控制器trimer,一个相册imageList,在 ...

  7. C# ICSharpCode.SharpZipLib.dll文件压缩和解压功能类整理,上传文件或下载文件很常用

    工作中我们很多时候需要进行对文件进行压缩,比较通用的压缩的dll就是ICSharpCode.SharpZipLib.dll,废话不多了,网上也有很多的资料,我将其最常用的两个函数整理了一下,提供了一个 ...

  8. ASP.NET或WinFrom中获取汉子的拼音首字母

    1.获得一个字符串的每个字的拼音首字母构成所需的字符串 #region  获取首字母 /// <summary>         /// 这个办法是用来获得一个字符串的每个字的拼音首字母构 ...

  9. 小白学Linux(五)--VI/VIM编辑器

    我们操作文件,终究离不开编辑文件,对文件内容的编辑,Linux系统下,我们通常使用VI/VIM来编辑文件.VI是每个Linux都会自带的文本编辑器,VIM是VI的增强版,可能有些发行版本没有自带,可以 ...

  10. iOS App上线的秘密

    App上线需要准备几个证书:首先是是CSR证书,要创建这个证书需要在自己电脑上找到钥匙串访问(在应用程序->其他 里面).钥匙串访问->证书助理->从证书颁发机构请求证书如下: 创建 ...