如果只获取 bitmap 对象,那么图片占据的内存大小就是按原图的分辨率进行计算。但如果有通过 into(imageView) 将图片加载到某个控件上,那么分辨率会按照控件的大小进行压缩。
比如第一个,显示的控件宽高均为 500dp = 750px,而原图分辨率 1080452,最后转换后的分辨率为:750 314,所以图片内存大小:750 * 314 * 4B = 94200B;
比如最后一个,显示的控件宽高为 1920984,原图分辨率转换后为:1920 984,所以图片内存大小:1920 * 984 * 4B = 7557120B;
至于这个转换的规则是什么,我不清楚,有时间可以去源码看一下,但就是说,Glide 会自动根据显示的控件的大小来先进行分辨率的转换,然后才加载进内存。
但不管是 Glide,fresco,都不管图片的来源是否在 res 内,也不管设备的 dpi 是多少,是否需要和来源的 res 目录进行一次分辨率转换。
所以,我在图片内存大小这一章节中,才会说到,如果你使用了某个开源库图片,那么,那么理论就不适用了,因为系统开放了 inSampleSize 接口设置,允许我们对需要加载进内存的图片先进行一定比例的压缩,以减少内存占用。
而这些图片开源库,内部自然会利用系统的这些支持,做一些内存优化,可能还涉及其他图片裁剪等等之类的优化处理,但不管怎么说,此时,系统原生的计算图片内存大小的理论基础自然就不适用了。
降低分辨率这点,除了图片开源库内部默认的优化处理外,它们自然也会提供相关的接口来给我们使用,比如:
//fresco ImageRequestBuilder.newBuilderWithSource(uri) .setResizeOptions(new ResizeOptions(500, 500)).build()对于 fresco 来说,可以通过这种方式,手动降低分辨率,这样图片占用的内存大小也会跟着减少,但具体这个接口内部对于传入的 (500, 500) 是如何处理,我也还不清楚,因为我们知道,系统开放的 API 只支持分辨率按一定比例压缩,那么 fresco 内部肯定会进行一层的处理转换了。
需要注意一点,我使用的 fresco 是 0.14.1 版本,高版本我不清楚,此版本的 setResizeOptions() 接口只支持对 jpg 格式的图片有效,如果 png 图片的处理,网上很多,自行查阅。
Glide 的话,本身就已经根据控件大小做了一次处理,如果还要手动处理,可以使用它的 override() 方法。
总结最后,来稍微总结一下:
一张图片占用的内存大小的计算公式:分辨率 * 像素点大小;但分辨率不一定是原图的分辨率,需要结合一些场景来讨论,像素点大小就几种情况:ARGB_8888(4B)、RGB_565(2B) 等等。
如果不对图片进行优化处理,如压缩、裁剪之类的操作,那么 Android 系统会根据图片的不同来源决定是否需要对原图的分辨率进行转换后再加载进内存。
图片来源是 res 内的不同资源目录时,系统会根据设备当前的 dpi 值以及资源目录所对应的 dpi 值,做一次分辨率转换,规则如下:新分辨率 = 原图横向分辨率 * (设备的 dpi / 目录对应的 dpi ) * 原图纵向分辨率 * (设备的 dpi / 目录对应的 dpi )。
其他图片的来源,如磁盘,文件,流等,均按照原图的分辨率来进行计算图片的内存大小。
jpg、png 只是图片的容器,图片文件本身的大小与它所占用的内存大小没有什么关系。
基于以上理论,以下场景的出现是合理的:
同个 app,在不同 dpi 设备中,同个界面的相同图片所占的内存大小有可能不一样。
同个 app,同一张图片,但图片放于不同的 res 内的资源目录里时,所占的内存大小有可能不一样。
以上场景之所说有可能,是因为,一旦使用某个热门的图片开源库,那么,以上理论基本就不适用了。
因为系统支持对图片进行优化处理,允许先将图片压缩,降低分辨率后再加载进内存,以达到降低占用内存大小的目的
而热门的开源图片库,内部基本都会有一些图片的优化处理操作:
当使用 fresco 时,不管图片来源是哪里,即使是 res,图片占用的内存大小仍旧以原图的分辨率计算。
当使用 Glide 时,如果有设置图片显示的控件,那么会自动按照控件的大小,降低图片的分辨率加载。图片来源是 res 的分辨率转换规则对它也无效。
本篇所梳理出的理论、基本都是通过总结别人的博客内存,以及自己做相关实验验证后,得出来的结论,正确性相比阅读源码本身梳理结论自然要弱一些,所以,如果有错误的地方,欢迎指点一下。有时间,也可以去看看相关源码,来确认一下看看。
推荐阅读1. Android性能优化(五)之细说Bitmap