前面关于内存优化文章中我们也有提到过:Bitmap可以说是内存中的一个大胖子。它对内存的影响极大。
例如:一张1920 * 1080像素 如果按照ARGB_8888来显示的话(1920 * 1080 * 4byte)占用近8M的内存空间。仅仅一张图片就占用了我们应用8M内存资源。
其实作为现在从事Android开发,对于大多数情况,我们一般都会使用第三方库来获取、解码和显示应用程序中的位图,这些第三方库抽象出了大部分复杂问题。使得我们只需要简单的API调用即可完成位图的显示。
但是我们还是有必要去了解下Android中有关Bitmap的处理机制。以便日后在应对Bitmap问题上无从下手。
本篇主要与大家一起讨论下:Bitmap内存模型在Android版本迭代中发生的变化、以及内存占用大小与在Android中特殊情景。
一、Bitmap内存模型
随着Android版本的演进,对于Bitmap在Android内存模型,系统也有着不同的情况。
1、Android3.0之前
在Android3.0之前,Bitmap的内存模型是在Native层,此时不受JVM垃圾收集器的管理,主要是强调通过Bitmap.recycle()方式显示的调用以便回收Bitmap占用的内存空间。
注意:只有当确定当前Bitmap不再被引用的时候才可以调用此方法,否则会抛出:“Canvas trying to use a recycled bitmap”错误。
2、Android3.0之后
在Android3.0之后,Bitmap的内存模型回到Java Heap层,此时它受JVM垃圾收集器的管理。不再强调recycle(),主要强调Bitmap的复用,有关Bitmap的复用我们在下面进一步讨论。
有趣的是:Android 8.0之后Bitmap内存模型又回到了Native层(不知道为什么)。
二、Bitmap的复用
1、inBitmap
Android在3.0之后BitmapFactory.Options引入了inBitmap属性,设置该属性之后解码图片时会尝试复用一张已经存在的Bitmap,避免了内存的回收以及重新申请的过程,对于内存的平滑使用显然进一步提升,性能表现也更佳。但是却有以下几点限制:
2、inBitmap的限制
1)声明可被复用的Bitmap必须设置inMutable为true;
2)Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用;
3)Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig;
4)Android4.4(API 19)之后被复用的Bitmap的内存必须大于需要申请内存的Bitmap的内存;
5)Android4.4(API 19)之前待加载Bitmap的Options.inSampleSize必须明确指定为1。
三、Bitmap如何复用
上面我们谈到了Bitmap的复用以及对复用的限制。
Google在官方文档中也给出了详尽的例子,具体参考:
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html
1、inMutable:
可被复用的Bitmap的inMutable必须为true。
检查Bitmap是否能够复用的核心代码:
四、Bitmap占用内存
1、getByteCount()
返回可用于存储此位图像素的最小字节数。
getByteCount()方法是在API12加入的,代表存储Bitmap的像素需要的最少内存。API19开始getAllocationByteCount()方法代替了getByteCount()。
2、getAllocationByteCount()
返回用于存储此位图像素的已分配内存的大小。
API19之后,Bitmap加了一个Api:getAllocationByteCount();代表在内存中为Bitmap分配的内存大小。
3、getByteCount()与getAllocationByteCount()的区别
一般情况下两者返回是相等的,但是存在以下情况时则不然:
通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap真实占用的内存大小(即mBuffer的长度)。
故:Api19之后应该首选使用getAllocationByteCount来获取当前Bitmap占用内存大小
4、Bitmap.Config(一个像素占用的内存大小)
Bitmap.Config用来描述图片的像素是怎么被存储的?
ARGB_8888:每个像素4字节, 共32位,默认设置。
Alpha_8:只保存透明度,共8位,1字节。
ARGB_4444:分别占用4位,共16位,2字节。
RGB_565:RGB分别占用5、6、5位,共16位,2字节。
5、计算Bitmap占用内存大小
这个大家可能都会知道:像素数量 * 一个像素占用内存大小 例:1920 * 1080 * 4 约等于7.9M。
那么只是加载一张Bitmap,它占用的内存 = width * height * 一个像素所占的内存。这种说法没有问题,但是在Android中情况可能就不一定了,因为我们忽略了Density(密度,下面我们来看下它对内存占用大小的影响)。
五、BitmapFactory(Density对内存占用大小的影响)
1、decodeResource()加载一张图片真正占用内存大小
上面说到Bitmap占用内存大小计算方式,但是在Android系统中存在特殊情况,这个情况就是像素密度带来的影响(Density)。首先我们先来看下源码:
BitmapFactory.java:
inDensity是图片所在资源目录对应的像素密度
inTargetDensity是目标设备的像素密度
BitmapFactory.cpp:
如果图片所在资源密度对应的像素密度 != 目标屏幕像素密度,会对解码图片计算一个缩放值
此处可以看出:BitmapFatcory.decodeResource()加载一张图片实际占用内存大小,占用的内存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存。
六、总结
1、Bitmap内存模型
Android 3.0(API11)之前,Bitmap的像素数据存放在Native内存,而Bitmap对象本身则存放在Dalvik Heap中。而在Android3.0之后,Bitmap的像素数据也被放在了Java Heap中。
2、Bitmap内存回收
Android3.0之前主要强调Bitmap.recycle()显示释放Bitmap占用内存资源,在Android3.0之后主要强调Bitmap的复用。
3、Bitmap内存占用计算
在Android4.4之后推荐使用getAllocationByteCount()获取。
内存占用大小=width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存
4、Bitmap复用
BitmapFactory.Options.inBitmap注意不同版本的限制问题
5、Glide
Google强烈推荐使用Glide来做Bitmap的加载。
荐:https://juejin.im/post/58c3b29761ff4b005d906730
荐:https://developer.android.google.cn/topic/performance/graphics/manage-memory.html
网友评论