@946898963
2022-11-14T10:38:49.000000Z
字数 3497
阅读 1421
Bitmap
Android是一个内存相当吃紧的系统,那么在做程序的过程中使用内存就需要相当谨慎,而我们接触最大的大对象估计就是Bitmap了,下面就根据Bitmap.Config值的介绍来看下Bitmap在内存中存储的形式,在实际场景中选择合适的配置进行Bitmap存储。
Bitmap.Config代表了Bitmap可以的配置情况,配置描述的是这些像素信息是如何存储的,这会影响到了图片质量和透明度。
Bitmap.Config的属性,它是由一个enum(枚举)类型构成的,其中拥有6中枚举可以选择。我们选择其中最常用的4个来着重说明一下。下面是Bitmap.Config的源码:
public enum Config {
ALPHA_8 (1),
RGB_565 (3),
@Deprecated
ARGB_4444 (4),
ARGB_8888 (5),
RGBA_F16 (6),
HARDWARE (7);
final int nativeInt;
private static Config sConfigs[] = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
};
Config(int ni) {
this.nativeInt = ni;
}
static Config nativeToConfig(int ni) {
return sConfigs[ni];
}
}
public enum Config {
ALPHA_8 (1),
RGB_565 (3),
RGBA_F16 (6),
ARGB_8888 (5),
}
以上4种是我们最常用的属性设置,ARGB_4444头上有个被遗弃的注解,这个值在level 13的时候就已经不被建议使用了,因为他是一个低质量的配置,它被建议使用ARGB_8888,在Android 4.4及其以上的系统中,设置的ARGB_4444会被系统使用ARGB_8888替换。
首先,我们来看一下第一个,也就是ALPHA_8,这种枚举类型是可以充当swith语句中的case条件的,所以枚举用起来也特别方便。优点是在于它是线程安全的单利模式,也支持序列化。缺点就是大量使用会导致性能降低。
- ALPHA_8
每个像素储存一个透明度值,每个像素是占1个字节(8位),也就是后面8的由来。其次,它不储存任何颜色信息,只单纯做透明度的处理,这对于设置遮罩的图片用例十分有用。根据变量名称,我们可以初步的知道它要表达的信息,以下也是如此。
每个像素使用一个独立的alpha通道存储,该通道占用8bit,即:每个像素占用1byte = 8bit / 8内存。注意:此属性会导致RGB通道信息丢失,只剩下透明度,所以基本不会用到。
- RGB_565
RGB_565,如变量名所述。R代表Red,G代表Green,B代表Blue。那么565就是储存它们的方式,它的每个像素占用2个字节。红色占5位,绿色占6位,蓝色占5位(5位保存的值2的5次方也就是32种的可能性,比如00001,00002...。同理6位则有64种值)。虽然它能提供这么多种颜色值,但是唯独不可以透明。因为它没有提供保存透明度的通道,所以通常它用于一些不透明的图片的设置。
只编码RGB通道信息,没有透明alpha通道信息,Red红色通道信息占用5位内存,Green绿色通道信息占用6位内存,Blue蓝色通道信息占用5位内存。每个像素占用2 byte= (5bit + 6bit + 5bit) / 8 内存,支持2^16 = 65535种颜色。质量较好。
所以直接设置 RGB_565:
- 对于一张透明图片(png),内存、宽高不变,bitmap 也不会失去透明度。
- 对于一张非透明图片(png、jpg),宽高不变,内存减小。
copy 一遍可以减少内存,但生成的 bitmap 会失去透明度,透明处变黑。
copy 一遍可以减少内存,但生成的 bitmap 会失去透明度,透明处变黑。
val bmScaled = bmRaw.copy(Bitmap.Config.RGB_565, true)
如果不需要alpha通道,特别是资源本身为jpg格式的情况下,用RGB_565格式解码更加节省内存。
- ARGB_8888
也如其名,它提供了颜色和透明度的通道,综合了以上两种方式。它每个像素占用4个字节(显然图片的大小也增加),并且它每个都用8位来保存其颜色和透明度值。我们知道8位将有256种可能值(支持2^32 = 1600w种颜色),这意味着它的颜色值的可能性越多,图片的质量、显示效果将大大提高。所以,这也是被推荐的一种属性设置,这也是Bitmap默认的颜色配置信息。
- RGBA_F16
这个与上面变量名又有点区别,从F16就可以看出。它每个像素占用8个字节,但是它是以半浮点数存储的,那前面的F就说的通了。Google的注释里还指明这个属性非常适合用于广色域宽屏和HDR(高动态范围的图片)。所以以此来看,它所占用的内存是最高的,因此显示的效果也非常好。
RGB_565比ARGB_8888节省内存相信很多同学都知道,但是为什么RGB_565更节省内存?通过上面的介绍我们可以知道,这是因为RGB_565牺牲了alpha通道,不支持透明度,并且RGB每个通道信息较ARGB_888更少。
通常我们优化Bitmap时,当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。
Glide加载图片默认格式RGB565,Picasso为ARGB8888,默认情况下,Glide占用内存会比Picasso低,色彩不如Picasso鲜艳,自然清晰度就低。
bitmap内存占用计算
Android中bitmap的内存占用是跟图片的尺寸(高和宽)相关。一张图片的内存占用大致的计算公式如下:
占用内存 = 图像像素总和(width x height)再 x 每像素(bitmap config)占用的字节数
以下是通过代码准确计算:
public static int getSizeInBytes(@Nullable Bitmap bitmap) {
if (bitmap == null) {
return 0;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {//API 19
try {
return bitmap.getAllocationByteCount();
} catch (NullPointerException npe) {
// Swallow exception and try fallbacks.
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
return bitmap.getByteCount();
}
return bitmap.getHeight() * bitmap.getRowBytes(); //earlier
}
比如拿ARGB_8888来说,它所拥有的颜色种类就是红色:2^8,绿色:2^8,蓝色:2^8,也就是大约1600多万种颜色。这么多种颜色组成的一张图片,比如1920*1080像素的一张图,以ARGB_8888的方式来存储颜色值的话。那它的大小就是1920*1080*4(字节) =8294400(bytes)= 7.91兆(M)大约值。像现在的手机摄像头动不动就是上千万像素,拍出来的照片如果按默认的ARGB_8888 config加载,则至少是几十M的内存占用。
当然,如果用RGBA_F16的方式储存颜色值,那将在16M左右的一张图,这对手机来说已经是一张非常高清的图片了。
参考链接:
Android bitmap config你理解对了吗? (有实际使用过程中遇到的问题)
[Android]修改Bitmap的Config格式设置及其Config参数源码阅读(有实际使用过程中遇到的问题)
Android笔记:Bitmap.Config与内存占用的关系