[基础] 8. Bitmap的加载和Cache

本文介绍了Android中Bitmap的高效加载方法,包括BitmapFactory提供的四种加载方式及其Options配置,特别是如何利用inSampleSize进行图片缩放以避免OOM。此外,还讨论了Android中的缓存策略,包括LRU缓存的实现原理及DiskLruCache的应用。

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

Bitmap的高速加载

Android的Bitmap使用不当经常会出现OOM,因此图片的加载和缓存是非常重要的一环

四类加载方法

BitmapFactory提供了四类加载图片的方法,从不同来源加载出一个Bitmap对象,最终的实现是在底层实现,对应BitmapFactory的几个native方法。四类方法支持BitmapFactory.Options参数

  1. decodeFile:从文件系统加载,间接调用decodeStream
  2. decodeResource:从资源加载,间接调用decodeStream
  3. decodeStream:从输入流加载
  4. decodeByteArray:从字节数组加载

参考代码:

```
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inInputShareable = true;  
opts.inPurgeable = true;

BitmapFactory.decodeFile(path, opts);  

BitmapFactory.decodeResource(getResources(), R.drawable.img); 

BitmapFactory.decodeStream(getResources().openRawResource(R.drawable.img));//这里换了方法  
```

BitmapFactory.Options缩放

高效加载Bitmap的核心思想是采用BitmapFactory.Options来缩放图片,即按照一定的采样率来加载所需尺寸的图片
例如通过ImageView加载图片,很多时候ImageView并没有图片原始尺寸那么大,这时就需要加载缩小后的图片。

缩放主要用到了inSampleSize, 采样率

  • 必须大于1有缩放效果
  • 设置inSampleSize = n 缩放比例为1/ n的平方,因为采样率同时作用于宽高
  • 官方文档建议inSampleSize取值应该为2的指数,比如1,2,4,8,16等。如果不为2的指数,会向下取整。

例如一张1024*1024的ARG8888格式,内存为1024*1024*4(4M)。设置inSamplSize = 2,缩放后512*512*4 即1M

如何获取采样率:

  • 设置inJustDecodeBounds = true,获取图片原始宽高,此处不会真正加载图片。
  • 依据采样率规则结合目标View的所需大小计算 inSampleSize
  • 设置inJustDecodeBounds = false,加载图片

常用的获取采样率的代码

public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
    //First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, Options);

    //Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    //Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory,decodeResource(res, resId, options);
}

public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    if (reqWidth == 0 || reqHeight == 0) {
        return 1;
    }

    //Raw height and width of image
    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;

        // Caculate the largest inSampleSize value that is a power of 2 and
        // keeps both height and width larger than the requested height and width
        while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth){
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Android中的缓存策略

目前常用的缓存算法是LRU(Least Recently Used) 近期最少使用:当缓存快满时,会淘汰近期最少使用的缓存目标
系统中采用LRU算法的缓存有两种:LruCache(内存缓存)和DiskLruCache(磁盘缓存)

  1. LruCache 内部通过LinkedHashMap来实现LRU,提供了get, put, remove方法

    总容量大小是

    62 public class LruCache<K, V> {
    63    private final LinkedHashMap<K, V> map;
    ......
    104    /**
    105     * Returns the value for {@code key} if it exists in the cache or can be
    106     * created by {@code #create}. If a value was returned, it is moved to the
    107     * head of the queue. This returns null if a value is not cached and cannot
    108     * be created.
    109     */
    110    public final V get(K key) {
    111        if (key == null) {
    112            throw new NullPointerException("key == null");
    113        }
    114
    115        V mapValue;
    116        synchronized (this) {
    117            mapValue = map.get(key);
    118            if (mapValue != null) {
    119                hitCount++;
    120                return mapValue;
    121            }
    122            missCount++;
    123        }
    124
    125        /*
    126         * Attempt to create a value. This may take a long time, and the map
    127         * may be different when create() returns. If a conflicting value was
    128         * added to the map while create() was working, we leave that value in
    129         * the map and release the created value.
    130         */
    131
    132        V createdValue = create(key);
    133        if (createdValue == null) {
    134            return null;
    135        }
    136
    137        synchronized (this) {
    138            createCount++;
    139            mapValue = map.put(key, createdValue);
    140
    141            if (mapValue != null) {
    142                // There was a conflict so undo that last put
    143                map.put(key, mapValue);
    144            } else {
    145                size += safeSizeOf(key, createdValue);
    146            }
    147        }
    148
    149        if (mapValue != null) {
    150            entryRemoved(false, key, createdValue, mapValue);
    151            return mapValue;
    152        } else {
    153            trimToSize(maxSize);
    154            return createdValue;
    155        }
    156    }
    157
    158    /**
    159     * Caches {@code value} for {@code key}. The value is moved to the head of
    160     * the queue.
    161     *
    162     * @return the previous value mapped by {@code key}.
    163     */
    164    public final V put(K key, V value) {
    165        if (key == null || value == null) {
    166            throw new NullPointerException("key == null || value == null");
    167        }
    168
    169        V previous;
    170        synchronized (this) {
    171            putCount++;
    172            size += safeSizeOf(key, value);
    173            previous = map.put(key, value);
    174            if (previous != null) {
    175                size -= safeSizeOf(key, previous);
    176            }
    177        }
    178
    179        if (previous != null) {
    180            entryRemoved(false, key, previous, value);
    181        }
    182
    183        trimToSize(maxSize);
    184        return previous;
    185    }

    典型的初始化Code

    mLruCache = new LruCache<String, Bitmap>(Math.round(Runtime.getRuntime().maxMemory() / 10) {
        protected int sizeOf(final String key, final Bitmap value) {
          return value.getRowBytes() * value.getHeight();
        }
      };
  2. DiskLruCache 磁盘缓存,可查看Android 4.1.1 libcore下面的DiskLruCache源码和okhttp的DiskLruCache源码

常用的图片缓存库:Fresco,Glide,Picasso

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值