文章目录
前言
在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的尺寸
*/