Android Bitmap

Bitmap是Android系统中的图像处理中最重要类之一。Bitmap可以获取图像文件信息,对图像进行剪切、旋转、缩放,压缩等操作,并可以以指定格式保存图像文件。
在Android开发中,我们经常与Bitmap打交道,而对Bitmap的不恰当的操作经常会导致OOM(Out of Memory)。

1. Bitmap

Android中的Bitmap对象是对位图的抽象,它可以从文件系统、资源文件夹、网络等各种不同的来源获取。位图可以看做是像素点的集合,本质上就是通过一系列二进制位来描述一张图片,具有不同色彩格式的位图使用不同数量的二进制位来描述一个像素点,因而图片质量和图片大小也就不同。

2. Bitmap的颜色配置信息与压缩方式信息

Bitmap中有两个内部枚举类:Config和CompressFormat:

  • Config是用来设置颜色配置信息的
  • CompressFormat是用来设置压缩方式的。

2.1 Config

名称解释
Bitmap.Config.ALPHA_8颜色信息只由透明度组成,占8位。
Bitmap.Config.ARGB_4444颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位。
Bitmap.Config.ARGB_8888颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置。
Bitmap.Config.RGB_565颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位。

通常我们优化Bitmap时,当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。

2.2 CompressFormat

名称解释
Bitmap.CompressFormat.JPEG表示以JPEG压缩算法进行图像压缩,压缩后的格式可以是”.jpg”或者”.jpeg”,是一种有损压缩。
Bitmap.CompressFormat.PNG表示以PNG压缩算法进行图像压缩,压缩后的格式可以是”.png”,是一种无损压缩。
Bitmap.CompressFormat.WEBP表示以WebP压缩算法进行图像压缩,压缩后的格式可以是”.webp”,是一种有损压缩,质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%。美中不足的是,WebP格式图像的编码时间“比JPEG格式图像长8倍”。

3. Bitmap 究竟占多大内存

此处参阅Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?

Bitmap 在内存当中占用的大小其实取决于:

  • 色彩格式,前面我们已经提到,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节
  • 原始文件存放的资源目录(是 hdpi 还是 xxhdpi 可不能傻傻分不清楚哈)
  • 目标屏幕的密度
  • 最终输出的 outputBitmap 的大小是scaledWidth*scaledHeight

一张522*686的 PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B。

if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}

在我们的例子中,
scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696
scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

915 * 696 * 4 = 2547360

3. Bitmap的优化策略

3.1 Recycle

从上面的注解中我们可以看到,当确认了这个Bitmap实例在后续的操作中不再需要后可以调用recycle方法,该方法是不可逆的。

在Android2.3时代,Bitmap的引用是放在堆中的,而Bitmap的数据部分是放在栈中的,需要用户调用recycle方法手动进行内存回收,而在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。

3.2 LruCache

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

LRU 的工作原理,最近使用的会放进队列的头部,最久未使用的放进队列的尾部,会首先删除队尾元素。

如果你 cache 的某个值需要明确释放,重写 entryRemoved 方法;如果 key 相对应的 item 丢掉,重写create(),这简化了调用代码,即使丢失了也总会返回。

  • 该类是线程安全的
  • 该类不允许空值和空 key

Sample of using LruCache:

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;

// If you're running on Honeycomb or newer, create a
// synchronized HashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()) {
    mReusableBitmaps =
            Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}

mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {

    // Notify the removed entry that is no longer being cached.
    @Override
    protected void entryRemoved(boolean evicted, String key,
            BitmapDrawable oldValue, BitmapDrawable newValue) {
        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
            // The removed entry is a recycling drawable, so notify it
            // that it has been removed from the memory cache.
            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
        } else {
            // The removed entry is a standard BitmapDrawable.
            if (Utils.hasHoneycomb()) {
                // We're running on Honeycomb or later, so add the bitmap
                // to a SoftReference set for possible use with inBitmap later.
                mReusableBitmaps.add
                        (new SoftReference<Bitmap>(oldValue.getBitmap()));
            }
        }
    }
....
}

3.3 inSampleSize

其实有些类似缩小多少倍的感觉,不过变化是这样的:例如inSampleSize==4,那么长、宽都变为原来的四分之一,而像素数则变为原来的十六分之一,毕竟像素=长*宽

这里例举一个选择图像后通过inSampleSize修改图像大小的操作,在onActivityResult中进行的:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == SELECT_PIC_RESULT_CODE && resultCode == RESULT_OK && data != null) {
            Uri uri = data.getData();//获取图像地址
            // 在解码的时候避免内存的分配,它会返回一个null的Bitmap  
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            try {
                InputStream inputStream = getContentResolver().openInputStream(uri);
                BitmapFactory.decodeStream(inputStream, null, options);
                // 获取到原始图片的尺寸
                int height = options.outHeight;
                int width = options.outWidth;
                int sampleSize = 1;
                int max = Math.max(height, width);

                if (max > maxSize) {
                    int nw = width / 2;
                    int nh = height / 2;
                    while ((nw / sampleSize) > maxSize || (nh / sampleSize) > maxSize) {
                        sampleSize *= 2;
                    }
                }

                options.inSampleSize = sampleSize;
                options.inJustDecodeBounds = false;
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;

                mBitmap = BitmapFactory.decodeStream(getContentResolver().
                        openInputStream(uri), null, options);
                mIvProcess.setImageBitmap(mBitmap);

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

        }
    }

在程序中,调整图片的大小需要通过设置Options的inJustDecodeBounds属性为true,将图片的width和height属性读出来,我们可以利用这些属性对Bitmap进行压缩,同时通过Options.inSampleSize属性设置压缩比。

3.4 三级缓存

现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好。所以一个应用的图片缓存策略是很重要的。

什么是三级缓存

  • 网络加载,不优先加载,速度慢,浪费流量
  • 本地缓存,次优先加载,速度快
  • 内存缓存,优先加载,速度最快(LruCache)

三级缓存原理

  • 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中
  • 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片
  • 总之,只在初次访问新内容时,才通过网络获取图片资源

参考并感谢

  1. 玩转Android Bitmap
  2. Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
  3. Managing Bitmap Memory
  4. LruCache
  5. Android中图片的三级缓存策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值