Bitmap之内存管理

本文详细探讨了Android中Bitmap的内存管理,包括Bitmap简介、BitmapFactory.Options参数控制、内存占用、高效加载策略(采样率压缩)、缓存策略及内存复用。通过采样率压缩和缓存机制,可以有效减少Bitmap对内存的压力,防止因图片加载导致的OOM问题。了解这些机制有助于优化Android应用的性能。
摘要由CSDN通过智能技术生成

前言

在Android应用程序内存问题中,Bitmap内存问题,占了相当的比重,所以弄清楚Bitmap内存管理的机制原理就尤为重要。下面就分析总结一下,Bitmap内存相关的问题。

1.Bitmap简介

Bitmap 是 Android 系统中的图像处理中最重要类之一。Bitmap 可以获取图像文件信息,对图像进行剪切、旋转、缩放,压缩等操作,并可以以指定格式保存图像文件。

有两种方法可以创建 Bitmap 对象,分别是通过 Bitmap.createBitmap() 和 BitmapFactory 的 decode 系列静态方法创建 Bitmap 对象。

下面我们主要介绍 BitmapFactory 的 decode 方式创建 Bitmap 对象。

  • decodeFile 从文件系统中加载
    • 通过 Intent 打开本地图片或照片
    • 根据 uri 获取图片的路径
    • 根据路径解析 Bitmap:Bitmap bm = BitmapFactory.decodeFile(path);
  • decodeResource 以 R.drawable.xxx 的形式从本地资源中加载
    • Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.icon);
  • decodeStream 从输入流加载
    • Bitmap bm = BitmapFactory.decodeStream(stream);
  • decodeByteArray 从字节数组中加载
    • Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);

2.BitmapFactory.Options控制图片解码的参数

  • inSampleSize:这是表示采样大小。用于将图片缩小加载出来的,以免占用太大内存,适合缩略图。

  • inJustDecodeBounds:当 inJustDecodeBounds 为 true 时,执行 decodexxx 方法时,BitmapFactory 只会解析图片的原始宽高信息,并不会真正的加载图片

  • inPreferredConfig:用于配置图片解码方式,对应的类型 Bitmap.Config。如果非null,则会使用它来解码图片。默认值为是 Bitmap.Config.ARGB_8888

  • inBitmap:在 Android 3.0 开始引入了 inBitmap 设置,通过设置这个参数,在图片加载的时候可以使用之前已经创建了的 Bitmap,以便节省内存,避免再次创建一个Bitmap。在 Android4.4,新增了允许 inBitmap 设置的图片与需要加载的图片的大小不同的情况,只要 inBitmap 的图片比当前需要加载的图片大就好了。

  • inDendity

    表示这个bitmap的像素密度,根据drawable目录, 图片放在不同的资源目录,会有不同程度缩放

  • inTargetDensity

    表示要被画出来时的目标(屏幕)的像素密度,
    代码中获取的方式getResources().getDisplayMetrics().densityDpi

通过 BitmapFactory.Options 的这些参数,我们就可以按一定的采样率来加载缩小后的图片,然后在 ImageView 中使用缩小的图片这样就会降低内存占用避免【OOM】,提高了 Bitamp 加载时的性能。

这其实就是我们常说的图片尺寸压缩。尺寸压缩是压缩图片的像素,一张图片所占内存大小的计算方式: 图片类型*宽*高,通过改变三个值减小图片所占的内存,防止OOM,当然这种方式可能会使图片失真 。

3.Bitmap的内存占用

我们在使用图片的时候,选择 jpg、png或者webp,对内存会不会有影响呢?对同一张图片而言,更改jpg为png或其它格式,并不会改变它实际占用的内存大小。Bitmap的大小(在本地磁盘通过属性查看)与Bitmap在内存中占用的大小是两个概念,值也不相同。Bitmap内存占用大小为:width * height * 一个像素点占用的字节数(由Bitmap的像素存储格式决定),只要这些因素没变,占用内存是不会改变的。可通过调用getByteCount()来获取Bitmap占用内存的大小,该方法返回的是可用于存储此位图像素的最小字节数

在这里插入图片描述
将这三张图片拷贝至drawable-xxhdpi目录,调用getByteCount方法获取占用内存:

	/**
     * 解析图片
     */
    private void decodeBitmap() {
   
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_mv);
        Log.e("xpf", "decodeBitmap: icon_mv.jpg " + bitmap.getWidth() + "x" + bitmap.getHeight() + "x"
                + bitmap.getConfig() + ",内存总大小" + bitmap.getByteCount());
        Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.icon_mv_p);
        Log.e("xpf", "decodeBitmap: icon_mv_p.png " + bitmap1.getWidth() + "x" + bitmap1.getHeight() + "x"
                + bitmap1.getConfig() + ",内存总大小" + bitmap1.getByteCount());
        Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.icon_mv_w);
        Log.e("xpf", "decodeBitmap: icon_mv_w.webp " + bitmap2.getWidth() + "x" + bitmap2.getHeight() + "x"
                + bitmap2.getConfig() + ",内存总大小" + bitmap2.getByteCount());

    }

打印日志如下:

在这里插入图片描述

我们知道由于Android的资源加载机制,图片放在不同的drawable目录,会对图片进行不同程度的拉伸或缩放,如果将图片放到drawable-hdpi目录,图片内存占用内存信息如下:

在这里插入图片描述

很明显图片尺寸被放大/拉伸了,内存占用就变大了,与Android资源加载机制相关。有一定的优先级,与设备dpi有关,适配时需要注意。

匹配的优先级:

  • 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录
  • 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录。drawable-nodpi目录中的资源适用于所有密度的设备,不管当前屏幕的密度如何,系统都不会缩放此目录中的资源。因此,对于永远不希望系统缩放的资源,最简单的方法就是放在此目录中;同时,放在该目录中的资源最好不要再放到其他drawable目录下了,避免得到非预期的效果
  • 如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录。例如,最匹配目录是xxhdpi,更高密度的目录和nodpi目录查找不到后,就会依次查找drawable-xhdp、drawable-hdpi、drawable-mdpi、drawable-ldpi。

放大还是缩小:

  • 如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。
  • 如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。
  • 如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。
  • 如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。

像素存储格式中A代表透明度;R代表红色;G代表绿色;B代表蓝色,单个像素点占用的字节数如下表:

格式 意义 单个像素占用字节数
ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度 1
ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位 2
ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位 4
RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位 2

3.Bitmap的高效加载/内存压缩/采样率压缩

想象一下如果一张图片为800 * 800 的RGB_565加载到内存,占用内存大小为800 * 800 * 2=1.22M, 比如显示的Imageview大小为80 * 80 ,一个应用有数不清的图片,如果直接加载到内存,对内存的压力太大了,显然是不合理的。此时,就根据采样率来加载图片。采样率inSampleSize = n, 表示采样后的图片的宽高均为图片原始宽高的1/n, 其中n一般为2的指数,比如1,2,4,8,16等等。如果外界传递给系统的inSampleSize不为2的指数,那么系统会向下取整一个最接近2的指数来代替,比如3,系统会选择2来代替。通过采用率来加载图片,本质是根据一定比例对图片进行尺寸压缩,其流程大致如下:

  • 将BitmapFactory.Options的inJustDecodeBounds参数设为true,并加载图片
  • 从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数
  • 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
  • 将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片

需要注意的是inJustDecodeBounds参数设为true,BitmapFactory只会解析图片的的原始宽高信息,并不会去真正的加载图片。得到采样率后,inJustDecodeBounds参数设为false,再去真正加载图片,加载的图片就是最终缩放后的图片。

/**
 * Bitmap高效加载,采样率压缩,本质是调整bitmap的尺寸
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值