乐趣区

关于android:Android-加载图片占用内存分析

本文首发于 vivo 互联网技术 微信公众号

链接:https://mp.weixin.qq.com/s/aRDzmMlkqB14Ty67GJs9vg

作者:Xu Jie

不同 Android 版本,对一张图片的内存解决形式是不一样的,应用不正确会导致 OOM 的产生,这篇文章带你梳理内存占用状况,抉择适宜你的图片加载模式,解决 OOM 问题。

一、背景

你晓得吗

  1. 一张 5.48MB,宽高像素为 4896*6528 的 24 位的动态图片,放在 Android 工程目录上面的 res/drawable-[density]/ 不同文件夹上面,占据的内存是多少?
  2. 应用 Glide 加载一张 5.48MB,宽高像素为 4896*6528 的 24 位的网络图片,占据内存又是多少?

(图:像素为 4896*6528 的图片)

二、梳理概念

在正式剖析上面的内容前,先来看几个概念。

1、屏幕尺寸

指屏幕的对角线的长度,单位是英寸,1 英寸 =2.54 厘米。这个值是利用手机屏幕的长和宽,而后利用勾股定理,就能够算出斜边的长了。

2、屏幕像素密度

即每英寸屏幕所领有的像素数,英文简称 ppi, 屏幕像素密度与屏幕尺寸和屏幕分辨率无关,屏幕密度越低在给定物理区域的像素就会较少。Android 将所有屏幕密度分为六组通用密度:ldpi(低)、mdpi(中)、hdpi(高)、xhdpi(超高)、xxhdpi(超超高)和 xxxhdpi(超超超高)。

3、屏幕分辨率

屏幕分辨率是指在横纵向上的像素点数,单位是 px,1px= 1 个像素点,比方咱们常常说的宽高像素为:4896*6528。

下面三个概念含糊吗?咱们能够看一下上面这两张图, 就能够理清下面三个概念了:

(图:分辨率计算公式)

上面的剖析,重要理解的是屏幕像素密度。

三、屏幕密度(dpi)对应关系

屏幕物理区域中的像素量,通常称为 dpi(每英寸点数)。屏幕密度越低在给定物理区域的像素就会较少。Android 将所有屏幕密度分为六组通用密度:ldpi(低)、mdpi(中)、hdpi(高)、xhdpi(超高)、xxhdpi(超超高)和 xxxhdpi(超超超高)。

六种通用密度之间遵循 3:4:6:8:12:16 的缩放比率。

四、代码验证

代码很简略,就是用一个 ImageView 蕴含一张背景图片,而后通过转换为 Bitmap 查看占用内存大小。

布局文件 activity_main.xml

布局文件,就是一个 ImageView 控件,蕴含一张背景图。

MainAcivity.java

Android 有一个非凡的文件夹 res/drawable-nodpi/,放在外面的资源,不会被放大或者压缩,依照原大小展现,咱们这里也把测试资源放在这个文件夹。

五、图片的内存占用

1、动态图片不辨别文件夹内存占用

依然以宽高像素为:4896*6528=31961088 的图片举例,图片原始大小为 5.48M,图片资源放在 res/drawable-nodpi/ 上面,这时候找一个 vivo X21 手机,加载这张图片,占据内存状况为 127844352byte:

而图片的原始图片像素总数为 31961088,跟内存大小 127844352byte 如同没什么关系,然而假相是 31961088* 4 = 127844352(Byte),原始图片尺寸大小与最终的内存占用大小呈倍数的关系,所以在这里与内存占用大小有间接关系的就是原始图片尺寸大小(例如:480×800),情理我都懂,然而倍数关系是从哪里来的呢,这就要议论到 Bitmap 的像素格局了。

Android 零碎反对 4 种格局的像素格局,源码在 Bitmap.Config 中:

为了保障图片品质,官网默认应用 ARGB_8888 格局,导致图片的每个像素会占用 4 个 Byte 大小,所以 demo 外面的图片占用内存大小就是像素总数 像素格局,就是 384000 4 = 1536000(Byte),这个时候应该有点成就感了,能够帮忙你解决一部分理论我的项目问题了。

2、动态图片辨别文件夹内存占用景象

(1)动态图片辨别文件夹在 X21(Android 8.0)上的内存占用

那么问题又来了,放在 res/drawable-nodpi/ 文件夹下没问题,放在其余文件夹下呢?因为咱们要适配不同的机器。

依然以 vivo X21 举例,x21 的指标图片文件夹是 res/drawable-xxdpi/,屏幕密度 480dpi。

看一下这个图片放在不同的文件夹上面,内存占用状况,单位:M。

能够看到,

  1. 对于分辨率为 res/drawable-hdpi/、res/drawable-xhdpi/、res/drawable-xxdpi/ 三个分辨率来说,图片占据内存根本是统一的,Java 层内存没有耗费,而是耗费了 native 内存。
  2. res/drawable-xxxdpi/ 分辨率上面的图片,占据内存是最高的,native 占据了 200M。

(2)所有的机器,内存占用都是这个法则吗

或者你有这个疑难:

为什么在不同的文件夹上面,图片占据的内存资源基本一致,有的时候却发现不同文件夹上面,内存占据又是不一样的?

在答复这个问题前,你要搞清楚,google 在图片加载时候,不同的 Android 版本,做了 native 堆栈和 Java 堆栈的辨别。

这里也有个有意思的景象,在 Android4.4 到 Android 8.0 以下的机器,当你把这个图片放在不同的文件夹上面时,图片占据的内存是不一样的,那是因为图片内存的加载,是在 Java 堆栈,所以你可能会遇到 Java 层面的 OOM。

AndroidRuntime: java.lang.RuntimeException: Canvas: trying to draw too large(127844352bytes) bitmap.

8.0 之后的内存调配是在 native,Java 层的 bitmap 创立之后,实际上像素内存的调配是在 native 层间接调用 calloc,所以其像素调配的是在 native heap 上,这也是为什么 8.0 之后的 Bitmap 耗费内存能够有限增长,直到耗尽零碎内存,也不会提醒 Java OOM 的起因。

3、网络图片加载内存占用景象

(1)Glide 加载图片的办法

glide 加载图片资源的形式有两个:

  • 无回调,应用如下形式加载
Glide.with(context)
        .load(url)
        .apply(requestOptions.override(width, height))
        .into(imageView);

有回调,应用上面加载形式,区别在 into 传入 simpleTarget,而不是 imageview

Glide.with(context)
        .asBitmap()
        .load(url)
        .apply(requestOptions)
        .into(simpleTarget);

其中的 simpleTarget 有两种定义形式:

  • 传入宽、高参数,且大于 0
simpleTarget = new SimpleTarget<Bitmap>(width, height) {}
  • 宽、高都为
simpleTarget = new SimpleTarget<Bitmap>() {}

(2)SimpleTarget 应用谬误带来的问题

  • A 和 B 的区别

区别就在于,当你传入了宽高的时候,图片就依照你传入的大小,缓存到了内存(Glide 更多级存储大小此处不探讨)。当你不设置宽、高的时候,图片就依照原始的像素大小进行了缓存。

  • 然而咱们常常不传入宽、高

这是因为加载网络图片的时候,咱们常常不晓得宽、高是多少,咱们设置本地资源 imageview 像素的时候,应用了 wrap_content 或者 match_content,不确定最终的宽高,所以咱们抉择传入 width = 0,height = 0,应用 glide 下载好图片后,再去做对应的设置。

  • 为什么咱们个别状况下感触不到 A、B 的差别

这是因为,网络图片也好、本地图片也好,像素都不会太大,以像素类型为 RGB_8888 为例,一个 1920*1080 的图片,在内存占据内存为 1920*1080*4Byte = 829440Byte = 7.9M。

此时设置宽、高(失常也就设置个几十 dp)与不设置宽高,区别并不大。

  • 解体来了

04-27 17:39:53.154 31269-31269/? E/art: Throwing OutOfMemoryError "Failed to allocate a 227278860 byte allocation with 1048576 free bytes and 126MB until OOM"
  • 为什么解体?

因为本地的一张图片大小尽管为 5.48M,像素为 width = 4896 height = 6528,然而在内存占据大小为 4896 6528 4 = 127844352byte = 120M。这个内存足以使官网 app 在原本应用内存就高的状况下闪退。

看一下加载这个本地图片时的内存状况,从 320M 到 548M,飙升 228M(还有后盾事件带来内存稳定,引起闪退的根本原因是 Graphics 的内存飙升)。

  • 怎么解决解体?

想方法去掉 simpleTarget 的 B 定义方法

如果你不晓得须要事实的资源宽高是多少,设置上面这个参数,这样就以以后屏幕宽、高作为最高显示像素,downsample 设置为 DownsampleStrategy.AT_MOST。

这个示意:

当你的资源原始尺寸大于 width height(屏幕宽、高像素)时,以 width height 为准。

当你的资源原始尺寸小于 width * height 时,以原始尺寸为准。

width * height 作为图片保留到内存时的最大像素值。

闪退问题同样解决,此时内存应用状况从 290M 到 340M,减少 50M(还有后盾事件带来内存稳定)。

六、总结

  1. 不同分辨率的动态资源图片放在不同的文件夹上面,不要轻易放,会引起内存的异样。
  2. 网络加载框架 Glide 等,最好依据屏幕宽、高设置须要加载的图片宽、高,不要应用图片原始大小加载,否则容易呈现解体。

其余:如果你有趣味,能够验证 Android 8.0 以下图片内存占用状况,会发现不一样的天地。

退出移动版