Android - Bitmap 的加载和 Cache

2017-08-18  本文已影响0人  DJN_

Android - Bitmap 的加载和 Cache

Android 应用可使用的最大堆内存受到 Android 系统的限制,通常情况为 16 M - 48 M,当应用使用的内存超出限制时就会引发内存溢出错误:java.lang.OutOfMemoryError

大多数应用中都会涉及到 Bitmap ,而 Bitmap 往往会占用较多的内存,因此实现高效加载的同时控制好内存占用是加载 Bitmap 时需要遵循的原则。

缓存在很多开发场景中都会涉及,实际开发中经常需要对 Bitmap 进行缓存,通过缓存 Bitmap,能够极大的提高图片加载效率以及用户体验,同时能够减轻内存负担。

1 Bitmap 的加载

Bitmap 指的是一张图片,可以是 png 格式也可以是 jpg 等其他常见的图片格式。
android 中通常使用 BitmapFactory 来加载 Bitmap,BitmapFactory 提供了如下几种方法从不同位置加载 Bitmap:

1.1 如何高效加载 Bitmap?

上面 BitmapFactory 的几个加载方法中每种加载方法都会有一个包含 BitmapFactory.Options 参数的重载方法,BitmapFactory.Options类可以将位图的尺寸按需进行压缩,只加载与 ImageView(或其它控件)尺寸相同的压缩尺寸后的位图,这样就可以将加载位图的大小压缩从而在一定程度上避免 OOM。

使用BitmapFactory.Options加载位图时主要用到它的 inSampleSize 参数,即采样率,当 inSampleSize 为 1 时,采样后的图片大小与原始图片的大小相等;当 inSampleSize 大于 1 ,比如 2 时,采样后的图片宽高都为原始图片的 1/2 ,而像素为原始图片的 1/4,。

比如有宽高为 1280 * 720 的一张图片,此时所占内存为 1280 * 720 * 4,当 inSampleSize 为 2 时,其正真加载尺寸为 640 * 360,所占内存为 640 * 360 * 4。

官方文档指出,inSampleSize 的取值应该总是 2 的指数,当不是指数时将向下取整选择最接近的 2 的指数代替。

调整方法如下:

//调整bitmap 大小
    public static Bitmap bitmapResizeFromResource(Resources res, int id, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();

        //只解析图片的原始宽高,而不正真加载图片
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, id, options);

        int inSampleSize = 1;
        if (reqWidth > 0 && reqHeight > 0) {
            final int height = options.outHeight;
            final int width = options.outWidth;

            //如果宽或高有任一者不满足要求就进行调整
            if (height > reqHeight || width > reqHeight) {

                // inSampleSize 为 1 没有作用,使从 2 开始增加
                final int halfHeight = height / 2;
                final int halfWidth = width / 2;

                while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth)
                    inSampleSize *= 2;
            }
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeResource(res, id, options);
    }

2 Android 中的缓存策略

缓存策略在 Android 开发中有着广泛的使用场景,尤其在图片加载这个场景。

当程序第一次从网络(或其它来源)加载图片后,就将其缓存到存储设备上,这样下次使用时就不需要再从网络上下载,很多时候为了提高用户体验,往往还会把图片在内存中再缓存一份,这样当应用打算从网络请求一张图片时,程序首先会从内存中去获取,如果内存中没有就去存储设备中获取,如果存储设备中也没有就从网络上下载。

缓存策略并没有统一的标准,一般来说,缓存策略主要包含缓存的添加、获取和删除。缓存的添加和获取都比较好理解,那么为什么要删除呢?这是因为不管是内存缓存还是存储缓存,他们的大小都是有限制的(移动设备的内存可用容量和存储设备,如 SD 卡等的容量也是有限的),当缓存装满时,就需要通过缓存算法选择部分缓存数据删除,以添加新的缓存数据。

目前常用的缓存算法是 LRU (Least Recently Used)算法,即最近最少使用算法。这个算法名字是不是很耳熟?《计算机操作系统》在 存储器管理 一章中介绍页面置换算法时就有 LRU 算法。

2.1 LruCache

LruCache 是 Android 提供的一个缓存类,LruCache 时一个泛型类,它内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象,其提供了 get 和 put 方法来完成缓存的获取和添加,当缓存满时,LruCache 会移除较早使用的缓存对象,然后再添加新的缓存对象。

LruCache 支持删除操作,通过 remove 方法可以删除一个指定的缓存对象。

2.2 弱引用、软引用、虚引用、强引用

很早 java API 就添加了弱引用和软引用,引用类在垃圾回收工作的过程中有种要作用。

2.2.1 强引用(Strong Reference)

平常我们开发时创建对象的方式大都是以强引用的方式创建,如String str = "abc"中变量 str 就是字符串对象"abc" 的一个强引用。
如下代码会移除 str 的强引用:str = null,此时垃圾回收器就会在某一时刻回收该对象。
强引用可以阻止垃圾回收器回收对象。

2.2.2 弱引用 (WeakReference)

你可以通过如下方式创建一个软引用对象:
String str = "test"
WeakReference<String> wr = new WeakReference<String>(str);
如果一个对象只有弱引用指向他,当移除强引用时(str = null),垃圾回收器会立即回收该对象。
弱引用无法阻止垃圾回收器回收对象。

2.2.3 软引用(SoftReference)

String str = "test";
SoftPreference sr = new SoftPreference(str)
如果有软引用指向对象,当移除强引用时(str = null),对象不会立即被回收,只有在 JVM 需要内存时,才会回收该对象。
软引无法阻止垃圾回收器回收对象,但可以延迟回收

2.2.4 虚引用{PhantomReference}

String str = "test"
PhantomReference pr = new PhantomReference(str)
当移除强引用时(str = null),拥有虚引用的对象可以在任何时刻被垃圾回收器回收。
虚引用无法阻止垃圾回收器回收对象。

参考文章:Java中弱引用、软引用、虚引用及强引用的区别

2.3 DiskLruCache

DiskLruCache 用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存。

DiskLruCache 得到了 android 官方文档的推荐,但它不属于 Android SDK 的一部分,源码可在这里获得:DiskLruCache.java

DiskLruCache 的使用不做介绍,可参看这篇文章:Android DiskLruCache完全解析,硬盘缓存的最佳方案


上一篇 下一篇

猜你喜欢

热点阅读