降低图片的内存消耗

我们知道产生OOM的原因是内存的使用量持续增长,直到超过了内存使用上限。每部手机对应用程序的内存上限在出厂时就已经是固定的了,所以我们需要尽量控制内存的使用量不要超过这个内存阀值,才能避免OOM。具体的做法就是去减少对象对内存的消耗。而今天要讨论的主要是减少图片对内存的消耗,因为图片消耗内存比较大,大多数应用程序都会有大量图片的加载,产生OOM往往也会发生在加载大量图片的时候。所以对图片占用内存的管理优化是很有必要的。但要记住的一点是,在内存的持续增长中,图片确实是其中一个很重量级的角色,但不是唯一的诱因,比如不好的代码写法导致产生大量对象或者内存泄露。这里说的只是降低图片对内存的消耗,谈不上解决OOM,因为图片一多,还是会产生OOM,只是推迟了问题产生的时间而已。


一、从网上获取到的图片的大小和图片占用的内存是不一样的

比如在我们图片列表里要加载这样一张图片:https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg 打开链接查看图片属性


看到大小是23.44KB,尺寸即宽高是240px x 240px。那当程序解码这张图片时申请的内存大小也是23.44KB吗?这里要注意的是,我们所要加载的网络图片的大小并不是Bitmap对象占用的内存大小!图片在内存中的大小的计算方式是:图片长(px) x 图片宽(px) x 单位像素占用的字节数。这张图片网页上显示的大小是24007字节,23.44KB,但是读到内存中(使用ARGB_8888颜色类型),他占用的内存大小是240x240x4=230400字节,225KB。


二、怎样查看或获取图片占用的内存大小?

1.在一、中用到的通过内存大小的计算方式直接计算得出:

图片的长 x图片的宽 x 单位像素占用的字节数

Android中不同的图片格式,他的单位像素占用的字节数不同。图片格式总共有四种:

Bitmap.Config = ALPHA_8 一个像素占1个字节

Bitmap.Config = ARGB_4444 一个像素占2个字节

Bitmap.Config = ARGB_8888 一个像素占4个字节(默认)

Bitmap.Config = ARGB_565 一个像素占2个字节

所以一个尺寸为400px x 800px的图片,如果采用ARGB_8888的颜色类型,那么计算得到的内存占用量即为 400x800x4 = 640000字节,即625kb。两张这样的图片的内存开销就已经超过1M了。

2、通过以下代码获取:
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public int getBitmapSize(Bitmap bitmap){

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){// API 19  Android 4.4
            return bitmap.getAllocationByteCount();
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12  Android 3.1
            return bitmap.getByteCount();
        }

        return bitmap.getRowBytes() * bitmap.getHeight();
    }
Android已经为我们提供了返回图片占用内存大小的方法了,调用该方法和我们通过计算得到的结果是一致的。

三、压缩图片
如果原图的尺寸比手机上要显示的图片尺寸要大,那么可以通过压缩图片来减少内存浪费。
我们要加载一张240x240的图片,如果设置了屏幕上显示120px*120px像素大小的ImageView,那么就没有必要将240px*240px的图片加载到内存中,造成不必要的内存浪费。把BitmapFactory.Options的inJustDecodeBounds设置为true,再去解析图片,就可以在decode的时候避免内存分配,他会返回一个null的Bitmap,虽然返回的Bitmap是null,但是他返回了原图的宽和高和类型。所以在构建Bitmap之前可以优先读取到图片的尺寸和类型,然后通过原图宽高和目标图的宽高计算出最终的缩放比例赋值给option.inSampleSize,再把option.inJustDecodeBounds的值设置为false,最后重新解析一次Bitmap,就可以达到压缩图片,节省内存的目的。
以解析网络流的图片为例:
    private Bitmap loadBitmap(String imageUrl) {
        HttpURLConnection connection = null;
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();

            BitmapFactory.Options options = new BitmapFactory.Options();
            //防止第一次解析时对图片的内存分配
            options.inJustDecodeBounds = true;
            //第一次解析
            BitmapFactory.decodeStream(connection.getInputStream(), null, options);
            //decodeStream不能重复解析同一个网络流的inputStream,需要重新打开一次inputStream
            connection.disconnect();
            connection = (HttpURLConnection) url.openConnection();
            //获取图片的缩放比例
            options.inSampleSize = calculateInSampleSize(options,100, 100);
            //将options.inJustDecodeBounds的值设回false,为了第二次解析能够正常分配内存
            options.inJustDecodeBounds = false;
            //第二次解析,此时图片的内存会按照压缩后的图片宽高去分配
            bitmap = BitmapFactory.decodeStream(connection.getInputStream(), null, options);
            L.d("loadBitmap url = "+imageUrl);
            L.d("该图占用内存 = "+getBitmapSize(bitmap)+"bytes, "+(getBitmapSize(bitmap)/1024)+"KB"+", width = "+bitmap.getWidth()+", height = "+bitmap.getHeight()+", config = "+bitmap.getConfig().name());

            return bitmap;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (connection != null){
                connection.disconnect();
            }
        }
        return bitmap;
    }

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int width = options.outWidth;//原图的宽
        int height = options.outHeight;//原图的高
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth){
            //计算出原图宽高和目标宽高的比例——缩小几倍
            int heightRadio = Math.round((float)height / (float)reqHeight);
            int widthRadio = Math.round((float)width / (float)reqWidth);
            //选择宽和高中最小的比例作为inSampleSize的值,可以保证最终图片的宽和高一定都会大于等于目标的宽和高
            inSampleSize = heightRadio < widthRadio ? heightRadio : widthRadio;
        }
        return inSampleSize;
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public int getBitmapSize(Bitmap bitmap){

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){// API 19  Android 4.4
            return bitmap.getAllocationByteCount();
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12  Android 3.1
            return bitmap.getByteCount();
        }

        return bitmap.getRowBytes() * bitmap.getHeight();
    }

图片压缩前加载图片后打印的内存占用量:


图片压缩后加载图片打印的内存占用量:


参考:



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值