Android性能优化之Bitmap优化

img

今天我们来讲讲关于Bitmap相关的优化。通常在开发中,Bitmap都是让人头痛的问题,它是内存消耗大户,经常会由于Bitmap的内存泄露,而导致内存溢出;同时,它也是本地存储的消耗大户,现在手机的像素越来越大,一张图片动不动都是好几兆的大小,不仅仅占用本地存储,如果,将其让在网络中传输,特别是在用户被动的情况下,那画面真是太美了。

鉴于Bitmap在开发都带来的种种棘手的问题,就迫使我们在做性能优化时,不得不考虑Bitmap的优化。今天,我们会从质量压缩尺寸压缩采样率压缩以及终极压缩等方面来对Bitmap来进行优化。

质量压缩优化

在质量压缩优化里,主要用到的就是我们Bitmap自带的compress(CompressFormat format, int quality, OutputStream stream)里的参数quality,这个参数的值为0~100

质量压缩并没有改变像素的多少,也就是说质量压缩并不会减少像素,而在内存里,Bitmap所占用的内存大小,是与我们图片像素的多少密切相关的。所以,质量压缩改变的只是保存到本地时,实际图片的大小,而对内存这一块并没有改变什么。

private void qualityCompress(Bitmap bitmap) {
    try {
        int quality = 20;
        File file = new File("/sdcard/img/", "质量压缩.jpg");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);

        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(baos.toByteArray());
        fileOutputStream.flush();
        fileOutputStream.close();

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

原图3.7M,通过quality=20质量压缩后,剩下270k.这里的原图指的是加载到内存后的Bitmap的大小。

我们都知道,像jpg这样的图片都是图片压缩算法后的图片,所以,我把jpg的图片加载到内存后,通过quality=100,将其变成原图后输出,而直接从网上下载下来的jpg文件,原本只有167k

尺寸压缩优化

尺寸压缩主要是通过改变图片像素来达到减少图片占用内存的大小。这里我们一般会创建一个空的bitmap,这个空的bitmap的宽和高都是通过比例压缩后的宽和高。然后,通过Canvas将原bitmap画到新的Bitmap上,而由于宽和高都是经过比例缩放后的值,所以,也就减小了Bitmap实际占用的内存大小。

原图的分辨率为:
img

而经过尺寸压缩后的图片分辨率为:
img

也就是宽和高分别减少为原来的1/4.而大小也从3.7M就了396K.注意了,这里的压缩不仅仅是占用的内存被压缩了,实际存储到本地的图片也被压缩了。

采样率压缩优化

采样率压缩主要是使用BitmapFactory.optionsinSampleSize来做的。这个其实有点类似于尺寸压缩,都是通过缩小图片的宽和高来减少像素达到减少内存占用和减少本地存储占用。但有点区别的是,尺寸可以针对宽和高分别进行设置缩放比例,而采样率压缩是对宽和高同时做等比例缩放。

使用采样率压缩,同时也要使用到BitmapFactory.options中的另外一个参数inJustDecodeBounds=true来在不加载图片到内存的情况下获取图片的宽高情况,然后通过原图的宽高和目标宽高得出一个比例值,用于inSampleSize来作用采样率来对图片进行缩放。

private void sampleSizeCompress() {
    int inSampleSize = 8;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;
    Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);

    //输出经过采样率优化后的图片
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
    File file = new File("/sdcard/img/", "采样率.jpg");
    try {
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(outputStream.toByteArray());
        fileOutputStream.flush();
        fileOutputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

终极压缩优化

这里主要会用于ndk相关的东西了。这里主要是跟Android系统采用的图片算法有关。

Android使用的是第二代JPEG图片算法引擎SKIA的阉割版,采用的是定长编码的方式来对图片进行编码,而这样处理的好处就是,消耗的资源比较少,不需要经过cpu大量的计算;缺点就是:因为使用的是定长编码,占用的空间就会比较大,同样的质量的图片需要更大的空间来存储。

而相比完整版的SKIA,阉割版的去除了哈夫曼编码。哈夫曼编码最大的特点是可变字长编码,通过对要编码的图片进行大量运算后,根据字符出现的概率进行编码,得到最佳编码。

而为什么Android没有采用Huffman编码呢?原因主要是,最初的Android设备配置差,不适合进行大量的cpu运算,被迫使用其他算法。而现在的Android设备的配置已经非常的高,完全可以引入哈夫曼编码来减小图片的存储大小。

目前,在微信中就用到了此编码。来解决图片传输过程中过大的问题。我们要解决的也就是绕过Bitmap API层,来自己编码实现,使用哈夫曼编码。

我们使用一个大神编译好的so库来演示一下效果吧。由于是别人编译的库,所以,Native方法所在的包名不能更改,因为so里面的jni接口已经在编译时被写死了。

项目地址:

https://github.com/allen218/BitmapCompress