Android端:优化Bitmap内存的几种方法

描述

作者 | Video++极链科技移动端Team秦鹏程

整理 | 包包

初识

Bitmap是图像处理的最重要类之一。用它可以获取图像文件信息,进行图像颜色变换、剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

许多 Android 开发者都对 Bitmap 不陌生,其作为显示图片的载体,会经常接触。而在日常开发中对图片的处理通常会用到第三方的开源库:Glide、Fresco、Picasso...,这些已经足够完善的工具不需要让我们考虑处理 Bitmap 的细节,这使得我们对其不是那么熟悉。

Bitmap 实实在在是内存使用的“大客户”。如何更好的使用 Bitmap,减少其对App内存的使用,是 Android 优化方面不可回避的问题,因此,本文从常规的 Bitmap 使用,到 Bitmap 内存计算,最后分析如何更有效的使用 Bitmap。

了解

Bitmap 占用了多大的内存

Bitmap 用来处理位图,每一张图片的每个像素点都会被读取,每个像素点的大小决定了 Bitmap 的内存大小。

所以计算内存大小的公式为:

占用的内存大小 = 像素总数量(宽x高)x 每个像素的字节大小

单个像素的字节大小

单个像素的字节大小由Bitmap的一个可配置的参数Config来决定。Bitmap中,存在一个枚举类Config,定义了Android中支持的Bitmap配置:

BITMAP


Android系统中,默认Bitmap加载图片,使用ARGB_8888模式。

Bitmap 占用内存大小实例

我们准备一张分辨率为 1920x1080,大小为 273KB 的 jpg 图片,放在手机的 SD 卡中,调用 BitmatFactory.decodeFile() 加载并显示到一个大小为 640x320 的 ImageView 中,占用的内存如下:


从计算内存大小的公式可以得到加载这张图片使用了大约 7m 的内存,即使是手机内存普遍上涨的今天,这样的开销也是法接受的。

在刚才的实例中,我们是将图片放在了手机的外置 SD 卡中,现在,我们将图片分别放到项目工程的 mipmap-xhdpi, mipmap-xxhdpi, mipmap-xxxhdpi 这三个资源目录中,调用 BitmatFactory.decodeResource() 加载到同样的 ImageView 中看看加载的情况:


我们发现,图片放在不同的资源目录中、使用不同的方法加载,占用的内存也会不同,为了探究这其中原理,需要通过观察 Bitmap.decode 的源码,这一过程是有 native 来完成的,所以我们找到 BitmapFactory.cpp#nativeDecode 开始跟踪,省略了其他不相关的代码:

BITMAP


上述代码中,最终 bitmap 是通过 canvas 绘制出来,而 canvas 绘制前有 scale 的操作 scale = (float) targetDensity / density; 这一行代码决定,即缩放的倍率和 targetDensity 和 density 相关,而这两个参数都是从传入的 options 中获取到的,再到 Bitmap.Options 中找到相关的参数:

• inDensity:Bitmap 位图自身的密度、分辨率

• inTargetDensity: Bitmap 最终绘制的目标位置的分辨率

其中 inDensity 和图片存放的资源文件的目录有关,同一张图片放置在不同目录下会有不同的值:

BITMAP


通过以上两个实例,我们得出了 decodeResource() 和 decodeFile() 的区别:

•decodeResource 用于读取Res、Raw等资源,得到的是图片的原始尺寸 * 缩放系数(inDensity)

•decodeFile 用于读取SD卡上的图,得到的是图片的原始尺寸

手动设置缩放系数

在 Bitmap.Options 中还有一个为 inScaled 的属性,如果设置为 false,则不进行缩放,如果设置为 true 或者不设置,则根据 inDensity 和 inTargetDensity 计算缩放系数。 如果你不想依赖于这个系统本身的 density,你可以手动设置 inDensity 和 inTargetDensity 来控制缩放系数:

BITMAP


压缩方式 inSampleSize & quality

inSampleSize 指的是压缩分辨率,取值必须为 2 的幂(当不为2的幂时,解码器会取与该值最接近的2的幂),例如,当 inSampleSize = 2 时,一张 1920x1080 的图片,将会被缩小为 960x540,相应的它的像素数和内存占用都被缩小为原来的 1/4。

quality 正如字面意思指的是图片品质,在代码中对应的 api 为:

BITMAP


CompressFormat 为 Bitmap 中的枚举类,有三个可用值:

• JPEG:表示以 JPEG 压缩算法进行图像压缩,压缩后的格式可以是 “.jpg” 或者 “.jpeg” ,是一种有损压缩。

• PNG:表示以 PNG 压缩算法进行图像压缩,压缩后的格式可以是 “.png” ,是一种无损压缩。

• WEBP:表示以 WebP 压缩算法进行图像压缩,压缩后的格式可以是 “.webp” ,是一种有损压缩,质量相同的情况下,WebP 格式图像的体积要比 JPEG 格式图像小40%。美中不足的是,WebP格式图像的编码时间“比JPEG格式图像长8倍”。

quality 为图片的品质,取值为 0-100,100 代表最高品质,不被压缩。另外,类似 PNG 这种无损格式会忽略 quality 的设置 stream 为图片被压缩后被保存在的输出流。

然而 Bitmap.compress 方法确实可以压缩图片,但压缩的是存储大小,即放到 disk 上的大小。

调试

现在我们通过几个实例,来验证一下以上的结论,首先来看一下两种压缩方式占用内存的影响:

inSampleSize

BITMAP


显示结果 :


以上 ImageView 的大小(640x320),用来加载 1920x1080 的图片确实有些浪费,所以经过计算,将原图压缩后发现图片占用内存的大小减少到原图的 1/10,如果原图本身与控件的大小相差不多,这时候还要缩放的话就会影响到图片显示的质量。

降低图片品质

BITMAP


显示结果 :


使用降低图片质量的方式压缩图片,可以发现尽管已经降低了 90% 的品质,图片也变得模糊,但其占用的内存与直接加载还是一样的。

改变 Bitmap.Config

我们已经知道, Bitmap 加载图片默认使用的 config 为 ARGB_8888,而且 ALPHA_8 是只有透明度的, 所以我们来看看改为 ARGB_4444 和 RGB_565 所显示的结果,只需要在 decode 的时候传入设置好的 options 参数,所以这里直接给出显示结果:


可以看到将 config 改为 ARGB_4444,所占用的内存与原图一样,而 RGB_565,变得是原图的 1/2,所以结论也不言而喻了,另外 ARGB_4444,已被官方标记为废弃。

总结

在上面,我们将一张 1920x1080 的图片,不做任何处理解析到内存中,将近占用的 7M,想象一下这样的开销发生在一个图片列表中,内存占用将达到非常夸张的地步。从之前Bitmap占用内存的计算公式来看,减少内存主要可以通过以下几种方式:

• 使用低色彩的解析模式,如RGB565,减少单个像素的字节大小。这样大约能减少一半的内存开销。Android 默认是使用 ARGB_8888 配置来处理色彩,占用4字节,改用RGB_565,将只占用2字节,代价是显示的色彩将相对少,适用于对色彩丰富程度要求不高的场景。

• 资源文件合理放置,高分辨率图片可以放到高分辨率目录下。和图片的具体分辨率有关,建议开发中,高分辨率的图像应该放置到合理的资源目录下,注意到Android默认放置的资源目录是对应于160dpi,目前手机屏幕分辨率越来越高,此处能节省下来的开销也是很可观的。理论上,图片放置的资源目录分辨率越高,其占用内存会越小,但是低分辨率图片会因此被拉伸,显示上出现失真。另一方面,高分辨率图片也意味着其占用的本地储存也变大。

• 图片缩小,减少尺寸。理论上根据适用的环境,是可以减少十几倍的内存使用的,它基于这样一个事实:源图片尺寸一般都大于目标需要显示的尺寸,因此可以通过缩放的方式,来减少显示时的图片宽高,从而大大减少占用的内存。


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分