原本计划是按照章节顺序学习《Android开发艺术探索》这本书的,Android性能优化这部分也是本书的最后一章。但是周末的时候,友盟线下反馈的公司项目的一个错误让我不得不提前学习这一块的知识。先看看线下反馈的错误吧:

android bitmap需要频繁的更换方向 怎么处理内存_Android

java.lang.OutOfMemoryError:应用程序内存溢出,俗称OOM,是指应用程序在申请内存时,没有足够的内存空间供其使用而出现的问题。Android中常见的导致内存溢出的场景有以下几种:

1.静态变量导致的内存溢出
2.单例模式导致的内存溢出
3.大量位图的加载导致的内存溢出

这里我们根据反馈上来崩溃日志,可以很清楚的看到我们这次的内存溢出是由第三种原因导致的。那么就从这个问题入手,找到解决Android中因Bitmap导致内存溢出的办法。

一.检测内存溢出

新版的Android Studio给我们提供了内存分析的可视化界面,但是精确的检测并找到内存泄漏的原因,我们还需要第三方的工具。

推荐简书上这篇文章,说的很详细,这里就不重复了。对照这篇文章一步一步进行即可:

使用新版Android Studio检测内存泄露和性能

二.分析具体原因
项目中应用其实很常见,在客户端选择图片以后,上传到七牛,然后再把七牛返回的url存储到服务器上。可问题就出现在图片上传到七牛的这一步,我们先看一下刚开始的代码怎么写的:

public static byte[] getSmallBitmap(String filePath) {
        Bitmap bitmap;
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        //获取缩放比例
        options.inSampleSize = calculateInSampleSize(options, 1000, 1000);
        options.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeFile(filePath, options);
        //bitmap转bytes
        byte[] bytes = Bitmap2Bytes(bitmap);
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return bytes;
    }

代码其实就是将Bitmap转化为byte数组,然后将这个byte数组传到七牛上,之前图片处理一直是这样做的,也没有出现任何问题。为什么这次却报了OOM呢,中场暂停一下,先来了解一下图片相关的知识:

以我红米2的测试机为例,一张图库里的图片分辨率是1080*1920px,它的文件大小为192KB,此时这张图片是以文件的形式存在于硬盘上。那么我们如果以Bitmap的形式将这张图片加载到应用程序中,占用的内存是多少呢:

1080*1920*4=8294400B=7.9M

图片(BitMap)占用的内存=图片长度 * 图片宽度*单位像素占用的字节数
前两个分别代表长度与宽度(像素单位),单位像素占用字节数其大小由BitmapFactory.Options的inPreferredConfig变量决定。
inPreferredConfig为Bitmap.Config类型,是个枚举类型,对应如下:

android bitmap需要频繁的更换方向 怎么处理内存_内存溢出_02

默认值为ARGB_8888。一张质量并不高的图片以Bitmap的形式加载到内存中占用的内存就快8M,当一次性加载大量位图的时候,肯定会远远超过应用程序所分配的内存空间,从而导致OOM。对于配置不高,系统版本过低,应用程序内存紧张的手机来说,出现这种情况的概率会大大增加。所以我们有必要对图片进行必要的压缩,减小内存,避免OOM。

三.优化过程:

图片的压缩分为两种:质量压缩与尺寸压缩,区别是质量压缩并不会改变图片的尺寸,而尺寸压缩则会改变图片的尺寸。那么它们分别应用在哪些地方呢,还是以刚才那张图片为例子。

当我把以文件形式存在在硬盘上的图片,以Bitmap的形式加载到内存中的时候,我就必须进行尺寸压缩,因为质量压缩并不会改变Bitmap所占内存的大小,而尺寸压缩由于是减小了图片的像素,所以它直接对bitmap产生了影响,从而使所占内存减小。代码具体实现过程:

public static byte[] getSmallBitmap(String filePath) {
        Bitmap bitmap;
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inSampleSize = calculateInSampleSize(options, 1000, 1000);
        options.inJustDecodeBounds = false;

        try {
            bitmap = BitmapFactory.decodeFile(filePath, options);
        } catch (Exception e) {
            options.inSampleSize = calculateInSampleSize(options, 500, 500);
            options.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeFile(filePath, options);
        }
        byte[] bytes = Bitmap2Bytes(bitmap != null ? bitmap : null);
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return bytes;
    }


   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 heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

代码分析:

Bitmap的实例化是通过BitmapFactory提供的接口生成的,利用BitmapFactory可以从一个指定文件中,利用decodeFile()解出Bitmap,也可以定义的图片资源中,利用decodeResource()解出Bitmap。它的主要方法及配置选项如下:

android bitmap需要频繁的更换方向 怎么处理内存_移动开发_03

实例化一个BitmapFactory.Options,并配置它的相关属性:

options.inJustDecodeBounds = true,表示解析图片的时候,只解析长度和宽度,不载入图片,这样就节省内存开支。
options.inPreferredConfig = Bitmap.Config.RGB_565,前文提到的表格一目了然,这样会节省一半的内存。
options.inSampleSize = calculateInSampleSize(options, 1000, 1000),计算缩放的比例,inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂,比如比例为7,向下寻找2的整数次幂,就是4。如果缩放比例是4的话,7.9M的那张图片最后占用的内存会是7.9/16=0.49M,完全不用担心OOM的发生。
options.inJustDecodeBounds = false,计算好压缩比例后,去加载解析原图。
bitmap = BitmapFactory.decodeFile(filePath, options),解析文件得到Bitmap。
与之前的代码相比,优化之后我改变了options.inPreferredConfig的值,并且在转换得到Bitmap的时候加上了try/catch,出现异常的话进一步扩大缩放比例,减小内存,防止OOM。

以上是尺寸压缩的相关办法,那么质量压缩又用在哪里呢:

private static byte[] Bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 75, baos);
        return baos.toByteArray();
    }

当图片从Bitmap的形式转化为二进制的形式(文件形式)时, 我们可以适当使用质量压缩,加快传递速率。还是之前那张图片,192KB的图片经过以上方法的质量压缩以后,大小为144KB左右(虽然图片大小变小,但是转换为Bitmap的时候,这张经过质量压缩后的图片所占内存还是不会变的,仍然为7.9M)。随着手机硬件配置的提升,手机图片的质量也越来越高,所以质量压缩还是很必要的。

以上就是从这次Bitmap导致的内存溢出学习总结到的一些知识,希望能对你有所帮助。下一篇再见~~~