Android中一张图片占据的内存大小是如何计算(1)

本文探讨了在Android开发中,图片的内存大小计算并非单纯基于文件大小,而是受加载时的分辨率转换和像素点数据格式影响。文章提供了一系列实验数据,解释了图片在不同场景下的内存占用差异,并提出了优化图片内存的两个主要方向:降低分辨率和调整像素点格式。
摘要由CSDN通过智能技术生成

Q3:同一张图片,在界面上显示的控件大小不同时,它的内存大小也会跟随着改变吗?

Q4:图片占用的内存大小公式:图片分辨率 * 每个像素点大小,这种说法正确吗,或者严谨吗?

Q5:优化图片的内存大小有哪些方向可以着手?

正文

在 Android 开发中,经常需要对图片进行优化,因为图片很容易耗尽内存。那么,就需要知道,一张图片的大小是如何计算的,当加载进内存中时,占用的空间又是多少?

先来看张图片:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是一张普通的 png 图片,来看看它的具体信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片的分辨率是 1080*452,而我们在电脑上看到的这张 png 图片大小仅有 55.8KB,那么问题来了:

我们看到的一张大小为 55.8KB 的 png 图片,它在内存中占有的大小也是 55.8KB 吗?

理清这点蛮重要的,因为碰到过有人说,我一张图片就几 KB,虽然界面上显示了上百张,但为什么内存占用却这么高?

所以,我们需要搞清楚一个概念:我们在电脑上看到的 png 格式或者 jpg 格式的图片,png(jpg) 只是这张图片的容器,它们是经过相对应的压缩算法将原图每个像素点信息转换用另一种数据格式表示,以此达到压缩目的,减少图片文件大小。

而当我们通过代码,将这张图片加载进内存时,会先解析图片文件本身的数据格式,然后还原为位图,也就是 Bitmap 对象,Bitmap 的大小取决于像素点的数据格式以及分辨率两者了。

所以,**一张 png 或者 jpg 格式的图片大小,跟这张图片加载进内存所占用的大小完全是两回事。**你不能说,我 jpg 图片也就 10KB,那它就只占用 10KB 的内存空间,这是不对的。

那么,一张图片占用的内存空间大小究竟该如何计算?

末尾附上的一篇大神文章里讲得特别详细,感兴趣可以看一看。这里不打算讲这么专业,还是按照我粗坯的理解来给大伙讲讲。

图片内存大小

网上很多文章都会介绍说,计算一张图片占用的内存大小公式:分辨率 * 每个像素点的大小

这句话,说对也对,说不对也不对,我只是觉得,不结合场景来说的话,直接就这样表达有点不严谨。

在 Android 原生的 Bitmap 操作中,某些场景下,图片被加载进内存时的分辨率会经过一层转换,所以,虽然最终图片大小的计算公式仍旧是分辨率*像素点大小,但此时的分辨率已不是图片本身的分辨率了。

我们来做个实验,分别从如下的几种考虑点相互组合的场景中,加载同一张图片,看一下占用的内存空间大小分别是多少:

  • 图片的不同来源:磁盘、res 资源文件
  • 图片文件的不同格式:png、jpg
  • 图片显示的不同大小的控件
  • 不同的 Android 系统设备

测试代码模板如下:

private void loadResImage(ImageView imageView) {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.weixin, options);
//Bitmap bitmap = BitmapFactory.decodeFile(“mnt/sdcard/weixin.png”, options);
imageView.setImageBitmap(bitmap);
Log.i(“!!!”, “bitmap:ByteCount = " + bitmap.getByteCount() + “:::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
Log.i(”!!!”, “width:” + bitmap.getWidth() + “:::height:” + bitmap.getHeight());
Log.i(“!!!”, “inDensity:” + options.inDensity + “:::inTargetDensity:” + options.inTargetDensity);
Log.i(“!!!”, “imageview.width:” + imageView.getWidth() + “:::imageview.height:” + imageView.getHeight());
}

ps:这里提一下,使用 Bitmap 的 getByteCount() 方法可以获取当前图片占用的内存大小,当然在 api 19 之后有另外一个方法,而且当 bitmap 是复用时获取的大小含义也有些变化,这些特殊场景就不细说,感兴趣自行查阅。反正这里知道,大部分场景可以通过 getByteCount() 打印图片占用的内存大小来验证我们的实验即可。

图片就是上图那张:分辨率为 1080*452 的 png 格式的图片,图片文件本身大小 56KB

序号前提Bitmap内存大小
1图片位于res/drawable,设备dpi=240,设备1dp=1.5px,控件宽高=50dp4393440B(4.19MB)
2图片位于res/drawable,设备dpi=240,设备1dp=1.5px,控件宽高=500dp4393440B(4.19MB)
3图片位于res/drawable-hdpi,设备dpi=240,设备1dp=1.5px1952640B(1.86MB)
4图片位于res/drawable-xhdpi,设备dpi=240,设备1dp=1.5px1098360B(1.05MB)
5图片位于res/drawable-xhdpi,**设备dpi=160,**设备1dp=1px488160B(476.7KB)
6图片位于res/drawable-hdpi,设备dpi=160,设备1dp=1px866880(846.5KB)
7图片位于res/drawable,设备dpi=160,设备1dp=1px1952640B(1.86MB)
8图片位于磁盘中,设备dpi=160,设备1dp=1px1952640B(1.86MB)
9图片位于磁盘中,设备dpi=240,设备1dp=1.5px1952640B(1.86MB)

看见没有,明明都是同一张图片,但在不同场景下,所占用的内存大小却是有可能不一样的,具体稍后分析。以上场景中列出了图片的不同来源,不同 Android 设备,显示控件的不同大小这几种考虑点下的场景。我们继续来看一种场景:同一张图片,保存成不同格式的文件(不是重命名,可借助ps);

图片:分辨率 1080*452 的 jpg 格式的图片,图片文件本身大小 85.2KB

ps:还是同样上面那张图片,只是通过 PhotoShop 存储为 jpg 格式

序号前提Bitmap内存大小比较对象
10图片位于res/drawable,设备dpi=240,设备1dp=1.5px4393440B(4.19MB)序号1
11图片位于res/drawable-hdpi,设备dpi=240,设备1dp=1.5px1952640B(1.86MB)序号3
12图片位于res/drawable-xhdpi,设备dpi=240,设备1dp=1.5px1098360B(1.05MB)序号4
13图片位于磁盘中,设备dpi=240,设备1dp=1.5px1952640B(1.86MB)序号9

这里列出的几种场景,每个场景比较的实验对象序号也写在每行最后了,大伙可以自己比对确认一下,是不是发现,数据都是一样的,所以这里可以先得到一点结论:

图片的不同格式:png 或者 jpg 对于图片所占用的内存大小其实并没有影响

好了,我们开始来分析这些实验数据:

首先,如果按照图片大小的计算公式:分辨率 * 像素点大小

那么,这张图片的大小按照这个公式应该是:1080 * 452 * 4B = 1952640B ≈ 1.86MB

ps: 这里像素点大小以 4B 来计算是因为,当没有特别指定时,系统默认为 ARGB_8888 作为像素点的数据格式,其他的格式如下:

  • ALPHA_8 – (1B)
  • RGB_565 – (2B)
  • ARGB_4444 – (2B)
  • ARGB_8888 – (4B)
  • RGBA_F16 – (8B)

上述实验中,按理就应该都是这个大小,那,为什么还会出现一些其他大小的数据呢?所以,具体我们就一条条来分析下:

分析点1

先看序号 1,2 的实验,这两者的区别仅在于图片显示的空间的大小上面。做这个测试是因为,有些人会认为,图片占据内存空间大小与图片在界面上显示的大小会有关系,显示控件越大占用内存越多。显然,这种理解是错误的。

想想,图片肯定是先加载进内存后,才绘制到控件上,那么当图片要申请内存空间时,它此时还不知道要显示的控件大小的,怎么可能控件的大小会影响到图片占用的内存空间呢,除非提前告知,手动参与图片加载过程。

分析点2

再来看看序号 2,3,4 的实验,这三个的区别,仅仅在于图片在 res 内的不同资源目录中。当图片放在 res 内的不同目录中时,为什么最终图片加载进内存所占据的大小会不一样呢?

如果你们去看下 Bitmap.decodeResource() 源码,你们会发现,系统在加载 res 目录下的资源图片时,会根据图片存放的不同目录做一次分辨率的转换,而转换的规则是:

新图的高度 = 原图高度 * (设备的 dpi / 目录对应的 dpi )

新图的宽度 = 原图宽度 * (设备的 dpi / 目录对应的 dpi )

目录名称与 dpi 的对应关系如下,drawable 没带后缀对应 160 dpi:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以,我们来看下序号 2 的实验,按照上述理论的话,我们来计算看看这张图片的内存大小:

转换后的分辨率:1080 * (240/160) * 452 * (240/160) = 1620 * 678

显然,此时的分辨率已不是原图的分辨率了,经过一层转换,最后计算图片大小:

1620 * 678 * 4B = 4393440B ≈ 4.19MB

这下知道序号 2 的实验结果怎么来的了吧,同样的道理,序号 3 资源目的是 hdpi 对应的是 240,而设备的 dpi 刚好也是 240,所以转换后的分辨率还是原图本身,结果也才会是 1.86MB。

小结一下:

位于 res 内的不同资源目录中的图片,当加载进内存时,会先经过一次分辨率的转换,然后再计算大小,转换的影响因素是设备的 dpi 和不同的资源目录。

分析点3

基于分析点 2 的理论,看下序号 5,6,7 的实验,这三个实验其实是用于跟序号 2,3,4 的实验进行对比的,也就是这 6 个实验我们可以得出的结论是:

  • 同一图片,在同一台设备中,如果图片放在 res 内的不同资源目录下,那么图片占用的内存空间是会不一样的
  • 同一图片,放在 res 内相同的资源目录下,但在不同 dpi 的设备中,图片占用的内存空间也是会不一样的

所以,有可能出现这种情况,同一个 app,但跑在不同 dpi 设备上,同样的界面,但所耗的内存有可能是不一样的。

为什么这里还要说是有可能不一样呢?按照上面的理论,同图片,同目录,但不同 dpi 设备,那显然分辨率转换就不一样,所耗内存应该是肯定不一样的啊,为什么还要用有可能这种说辞?

emmm,继续看下面的分析点吧。

分析点4

序号 8,9 的实验,其实是想验证是不是只有当图片的来源是 res 内才会存在分辨率的转换,结果也确实证明了,当图片在磁盘中,SD 卡也好,assert 目录也好,网络也好(网络上的图片其实最终也是下载到磁盘),只要不是在 res 目录内,那么图片占据内存大小的计算公式,就是按原图的分辨率 * 像素点大小来。

其实,有空去看看 BitmapFactory 的源码,确实也只有 decodeResource() 方法内部会根据 dpi 进行分辨率的转换,其他 decodeXXX() 就没有了。

那么,为什么在上个小节中,要特别说明,即使同一个 app,但跑在不同 dpi 设备上,同样的界面,但所耗的内存有可能是不一样的。这里为什么要特别用有可能这个词呢?

是吧,大伙想想。明明按照我们梳理后的理论,图片的内存大小计算公式是:分辨率*像素点大小,然后如果图片的来源是在 res 的话,就需要注意,图片是放于哪个资源目录下的,以及设备本身的 dpi 值,因为系统取 res 内的资源图片会根据这两点做一次分辨率转换,这样的话,图片的内存大小不是肯定就不一样了吗?

emmm,这就取决于你本人的因素了,如果你开发的 app,图片的相关操作都是通过 BitmapFactory 来操作,那么上述问题就可以换成肯定的表述。但现在,哪还有人自己写原生,Github 上那么多强大的图片开源库,而不同的图片开源库,内部对于图片的加载处理,缓存策略,复用策略都是不一样的。

所以,如果使用了某个图片开源库,那么对于加载一张图片到内存中占据了多大的空间,就需要你深入这个图片开源库中去分析它的处理了。

因为基本所有的图片开源库,都会对图片操作进行优化,那么下面就继续来讲讲图片的优化处理吧。

图片优化

有了上述的理论基础,现在再来想想如果图片占用内存空间太多,要进行优化,可以着手的一些方向,也比较有眉目了吧。

图片占据内存大小的公式也就是:分辨率*像素点大小,只是在某些场景下,比如图片的来源是 res 的话,可能最终图片的分辨率并不是原图的分辨率而已,但归根结底,对于计算机来说,确实是按照这个公式计算。

所以,如果单从图片本身考虑优化的话,也就只有两个方向:

  • 降低分辨率
  • 减少每个像素点大小

除了从图片本身考虑外,其他方面可以像内存预警时,手动清理,图片弱引用等等之类的操作。

减少像素点大小

第二个方向很好操作,毕竟系统默认是以 ARGB_8888 格式进行处理,那么每个像素点就要占据 4B 的大小,改变这个格式自然就能降低图片占据内存的大小。

常见的是,将 ARGB_8888 换成 RGB_565 格式,但后者不支持透明度,所以此方案并不通用,取决于你 app 中图片的透明度需求,当然也可以缓存 ARGB_4444,但会降低质量。

由于基本是使用图片开源库了,以下列举一些图片开源库的处理方式:

//fresco,默认使用ARGB_8888
Fresco.initialize(context, ImagePipelineConfig.newBuilder(context).setBitmapsConfig(Bitmap.Config.RGB_565).build());

//Glide,不同版本,像素点格式不一样
public class GlideConfiguration implements GlideModule {

@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}

@Override
public void registerComponents(Context context, Glide glide) {
}
}
//在AndroidManifest.xml中将GlideModule定义为meta-data

//Picasso,默认 ARGB_8888
Picasso.with(imageView.getContext()).load(url).config(Bitmap.Config.RGB_565).into(imageView);

以上代码摘抄自网络,正确性应该可信,没验证过,感兴趣自行去相关源码确认一下。

降低分辨率

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

[外链图片转存中…(img-tunMoCRd-1713785789283)]

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值