Android Bitmap 究竟占了多少内存

作者 : 开心源码 本文共4315个字,预计阅读时间需要11分钟 发布时间: 2022-05-12 共216人阅读

前言

在Android的内存优化中,对Bitmap的优化绝对是主角,由于Bitmap对内存的影响很大,稍有不慎就很容易引起OOM的问题。不信的话就随我来看看Bitmap究竟能吃掉多少内存。

预备知识

本篇文章不会讲到任何源码的东西,但还是需要有肯定的预备知识的。

Bitmap的色彩模式,目前常见的有两种模式:

  1. Config.RGB_565:565分别对应着表示RGB所需要的位数,加起来是16位,也就是一个像素需要2个字节来表示。这种模式下不支持Alpha通道。
  2. Config.ARGB_8888:这是默认的选项,每个通道占8位,所以一个像素需要4个字节来表示。这种模式质量最高,占的内存也高。

我们在Android上经常使用的单位是dp,1dp等于多少像素其实是与设施的密度有关系的,比方说我们现在最常见的1080 * 1920分辨率的手机,它的屏幕密度是480,对应起来,1dp = 3px,对应的资源目录是drawable-xxhdpi

density11.5234
densityDpi160240320480640
资源目录mdpihdpixhdpixxhdpixxxhdpi

Bitmap 占了多少内存

这个问题换成以前,我可能就会直接答复,很简单啊。假设这张图片是ARGB_8888的,那这张图片占的内存就是 width * height * 4个字节。调用Bitmap.getByteCount()返回的也是这个计算结果。
后来由于工作关系,接触到Bitmap比较多,才发现这个答复其实只答对了一半,答复正确只是由于碰巧而已。Bitmap占用的内存还跟屏幕密度有关系。接下来就是动手的试验求真知的阶段了。

前提条件:图片大小:450 * 337, 手机是1080P的,对应xxhdpi目录, 颜色模式为ARGB_8888,按照上面的算法算的话结果应该是 450 * 337 * 4 = 606600
步骤:将图片分别放在drawable,drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi,将加载出来的Bitmap大小以及占用内存打印出来。
结果:

资源目录drawabledrawable-xhdpidrawable-xxhdpidrawable-xxxhdpi
Bitmap大小1350 * 1011675 * 506450 * 337338 * 253
占用内存54594001366200606600342056

发现没有,只有放在drawable-xxhdpi目录的图片结果才跟我们上面算的一样。放在其它目录的结果是Bitmap大小变了,导致占用内存也相应的变化了。
造成这样的结果的起因就是上面提到的屏幕密度了。当图片放在drawable-xhdpi目录下,但是需要显示在xxhdpi设施上时,这张图片会被认为是低密度设施需要的,现在要显示在高密度设施上,需要做一个放大,带来的结果就是图片变大了,占用内存也变大了。
那么需要放大多少呢?也是跟图片放置的目录和手机的密度有关系。还是以这个例子来说,需要放大的倍数是:480 / 320 = 1.5,即宽和高都放大1.5倍。再来手动计算一次好了:450 * 1.5 * 337 * 1.5 * 4 = 675 * 505.5 * 4 = 1364850,跟计算结果相差一丢丢,但已经很接近了。注意到计算的过程中有浮点数,而结果是整数,所以应该考虑下是不是精度问题导致的了。其实真正的计算结果是这样的:

width = (int) (405 * 1f / 320 * 480 + 0.5f) = 675height = (int) (337 * 1f / 320 * 480 + 0.5f) = 506byteCount = 675 * 506 * 4 = 1366200

不要问为什么是这样子的,由于源码里的计算方式就是这样子滴。

从这个例子中,我们也可以看得出假如图片资源放错目录,可能会带来什么样的后果。特别是我们可能很容易的就把图片放到drawable目录下,由于这个是默认的目录。但实际上它代表的是drawable-mdpi。试想一下,假如我们把图片都放在这个目录下,而手机是xxhdpi的,那么每张图片的占用将是原来的9倍啊同学们!!所以在开发过程中肯定要注意把资源放置到正确的目录下。

上面说的是decodeResource的情况,而假如是decodeStream的话一般不会有上面的这种情况,所以计算方式就很直接很简单了。但是假如在解码时传入的options指定了inDensityinTargetDensity的话,那么情况又跟上面的例子相似了。

另外,图片的内存占用大小也受图片颜色模式的影响,假如我们把颜色模式设置为RGB_565,那直接即可以省下一半的内存了。对JPG格式的图片我们即可以考虑这样子做,由于它没有alpha通道。当然了,图片的质量也会下降少量,这个就需要去评估一下值不值得了。

到了这里,也算是解答完Bitmap占多少内存的问题了。不过这过程中又发现了一个有趣的问题。

getByteCount() & getAllocationByteCount()

在查看Bitmap的占用内存时,我发现了这两个很类似的api,于是就在打log的时候将这两个方法的结果都打了出来,结果发现都是一样的。但既然有两个api,就说明他们肯定是有什么区别的,于是就查了一下资料,也在这里做一个补充说明吧。
通常情况下,这两个api是没有区别的,但假如你做了Bitmap复用,那他们就开始有区别了。在Android 3.0之后,Android支持了Bitmap复用,也就是说旧Bitmap的内存可以直接给新Bitmap用,不用再去申请内存了,前提条件是这两张Bitmap占用的大小一样大。到了Android 4.0之后,这一条件放宽了,只需旧Bitmap占用的内存大于新Bitmap所需要的内存,即可以直接复用了。还是举一个例子:先加载一张大一点的图片,而后用这张图片去给一张小一点的图片复用:

val largeOption = BitmapFactory.Options()// 肯定要加上这行代码,否则不生效largeOption.inMutable = trueval largeBitmap = BitmapFactory.decodeResource(resources, R.drawable.large, largeOption)Log.d(TAG, "bitmap is $largeBitmap, bitmap size is (${largeBitmap.width}, ${largeBitmap.height}),  byteCount = ${largeBitmap.byteCount}, allocationByte = ${largeBitmap.allocationByteCount}")val smallOption = BitmapFactory.Options()smallOption.inBitmap = largeBitmapval smallBitmap = BitmapFactory.decodeResource(resources, R.drawable.small, smallOption)Log.d(TAG, "bitmap is $smallBitmap, bitmap size is (${smallBitmap.width}, ${smallBitmap.height}), byteCount = ${smallBitmap.byteCount}, allocationByte = ${smallBitmap.allocationByteCount}")

结果是:

bitmap is android.graphics.Bitmap@cd9eadf, bitmap size is (600, 600), byteCount = 1440000, allocationByte = 1440000bitmap is android.graphics.Bitmap@cd9eadf, bitmap size is (400, 250), byteCount = 400000, allocationByte = 1440000

可以看出,这两张Bitmap都是同一个对象来着,第一张Bitmap因为没有复用,所以byteCount == allocationByte。第二张Bitmap因为复用了第一张,byteCount表示当前Bitmap所占内存的大小,而allocationByte表示被复用Bitmap真实占用内存大小。所以假如还有新的Bitmap,只需它所需的内存小于allocationByteCount即可以了。再来试验一下:

// 这张Bitmap的大小介于largeBitmap和smallBitmap之间// 选择复用smallBitmapval normalOption = BitmapFactory.Options()normalOption.inBitmap = smallBitmapval normalBitmap = BitmapFactory.decodeResource(resources, R.drawable.normal, normalOption)Log.d(TAG, "bitmap is $normalBitmap, bitmap size is (${normalBitmap.width}, ${normalBitmap.height}), byteCount = ${normalBitmap.byteCount}, allocationByte = ${normalBitmap.allocationByteCount}")

得到的结果是:

bitmap is android.graphics.Bitmap@cd9eadf, bitmap size is (450, 337), byteCount = 606600, allocationByte = 1440000

Bitmap还是原来的对象,复用也成功了。所以假如要使用Bitmap复用,需要用到的应该是getAllocationByteCount()方法去判断是否做复用。写到这里,忽然想起了之前项目里用到的Bitmap复用,用的判断方法还是getByteCount()。尽管这样写也不会报错什么的,但是假如是 byteCount < 新Bitmap所需内存 < allocationByte这种情况的话,就会造成本可以复用的Bitmap却无法复用而需要去重新申请内存空间。不说了,等明年上班了赶紧改回来,这也算是写这篇文章的一个小收获了。

尾声

看了下上一篇的写作时间,才发现已经大半年没升级博客了,有点惭愧。这篇文章也算是为2018年划上一个句号吧,希望2019年能勤劳少量,多写写博客总结。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android Bitmap 究竟占了多少内存

发表回复