Android Bitmap 到底占了多少内存

前言

在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

density 1 1.5 2 3 4
densityDpi 160 240 320 480 640
资源目录 mdpi hdpi xhdpi xxhdpi xxxhdpi

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大小以及占用内存打印出来。
结果:

资源目录 drawable drawable-xhdpi drawable-xxhdpi drawable-xxxhdpi
Bitmap大小 1350 * 1011 675 * 506 450 * 337 338 * 253
占用内存 5459400 1366200 606600 342056

发现没有,只有放在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) = 675
height = (int) (337 * 1f / 320 * 480 + 0.5f) = 506
byteCount = 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 = true
val 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 = largeBitmap
val 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 = 1440000
bitmap 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之间
// 选择复用smallBitmap
val normalOption = BitmapFactory.Options()
normalOption.inBitmap = smallBitmap
val 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年能勤劳一些,多写写博客总结。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容