Bitmap加载与缓存相关

本文介绍Android中Bitmap的高效加载方法及内存缓存LruCache的使用,并涉及存储设备缓存DiskLruCache的基本用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
概述

     Bitmap在Android中指的是一张图片,可以是各种常见的图片格式。可以使用BitmapFactory类来加载一个图片。BitmapFactory提供了四类方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象。


高效加载Bitmap
    
     高效加载Bitmap的思想是,采用Bitmap.Options来加载所需尺寸的图片。通过options按一定采样率来加载缩小后的图片,可以降低内存占用,从而来一定程度上避免OOM。
     通过BitmapFactory.Options来缩放图片,用到它的inSampleSize参数(采样率)。inSampleSize必须是大于1的整数(值应总是2的指数,如果不是,会被向下取整并选择一个最接近2的指数来代替。小于1时会被当做1。),设置后可以将图片的宽高都缩小(1/inSampleSize)倍,相应地,缩放比例为(1/inSampleSize的2次方)。 

      获取采样率的流程:
          ①、将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片(设为true后,BitmapFactory只会解析图片的宽高信息,而不会真正加载图片,故这个操作是轻量级的)。
          ②、从BitmapFactory.Options中取出图片的原始宽高信息,对应于outWidth和outHeight。
          ③、根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
          ④、将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。

     代码如下:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize >= reqWidth)) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

          在上面的calculateInSampleSize是计算图片的采样率。使采样率为“宽或高的最大缩放值”中的较小的一个。


内存缓存(LruCache)

     目前常用的一种缓存算法是LRU(Least Recently Used,最近最少使用)。思想是,当缓存满时,优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LruCache(实现内存缓存)和DiskLruCache(实现存储设备缓存)。
     LruCache是Android 3.1提供的一个缓存类。内部采用 LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象。使用LinkedHashMap的原因是它保留了插入顺序,使得输出顺序和输入顺序一致,在put方法和get方法执行create之后,会调用到trimToSize方法来根据需要移除之前的缓存,使用LinkedHashMap有助于优先移除最近最少使用的缓存。
     使用LruCache只需要提供缓存的总容量大小并重写sizeOf方法即可。在需要的情况下,也可以重写entryRemoved方法,在其中完成一些资源回收工作。一个使用的例子如下:

private static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
private static int cacheSize = maxMemory / 8;
private static LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }
};
     
     下面看下它的put方法:

/**
* 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;
}
     比较简单。先判断key和value是否为null,是的话抛异常。否则计算要缓存的内容的大小,然后使用map.put方法将其加进去。map.put方法会替换并返回当前key之前已经保存的value(如果没有就返回null)。若返回的不是null,则说明map.put方法执行的是替换,故把size相应减小。同时回调entryRemoved方法。最后调用trimToSize方法,判断是否需要移除一些缓存以使缓存容量符合maxSize。可以看到put方法中添加缓存的代码块是由synchronized修饰的,故是线程安全的。

     再看下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;
    }
}
     get方法也比较简单。首先从map中提取key对应的缓存,如果存在就直接返回。如果不存在,就尝试去创建一个,我们可以通过重写create方法来进行创建(猜测用途是从存储设备中获取并缓存进内存)。创建成功后就把它添加进map中,思路和put方法中差不多。


存储设备缓存(DiskLruCache)
      基本用法:
          ①、首先创建DiskLruCache实例:
DiskLruCache mDiskLruCache = DiskLruCache.open(File directory, int appVersion, int valueCount, long maxSize);
          ②、获取SnapShot:
SnapShot snapShot = mDiskLruCache.get(key);
          ③、对snapShot第一次判空:
               I、若为空,则从网上下载:
DiskLruCache.Editor editor = mDiskLruCache.edit(key);  //打开Editor
OutputStream os = editor.newOutputStream(0);
...... //从网上下载,写入os输出流。
if(下载成功){
     editor.commit();
} else{
     editor.abort();
}
mDiskLruCache.flush();
              II、若不为空,则取缓存:
FileInputStream fis = snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fis.get(0);

bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
     以上是大致总结,更具体看郭霖博文: http://blog.youkuaiyun.com/guolin_blog/article/details/28863651


     


     

























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值