Android 图片资源的目录 -- 一个背景图片引发的内存 OOM

在开发中应用突然crash, 从日志上看是解压图片内存不足导致发生了OOM。

Caused by: java.lang.OutOfMemoryError: Failed to allocate a 85881612 byte allocation with 16777216 free bytes and 69MB until OOM

用Android monitor 看了下内存,打开这个Activity时,内存直接跳涨了60M左右。好崩溃。

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 oom_oom.png

看了下这个Activity有一个背景,背景为png图片。注释掉这个背景图片后,从Android Monitor 上看内存的曲线平滑了。原因就是这张图片了。但是一张图片也不能吃掉60M的内存。

res/mipmap/pc_car.png: PNG image data, 1440 x 1217, 8-bit/color RGBA, non-interlaced

分辨率大小为 1440×1217, 32为像素,解压后大小应该为6M, 还是不能解释上面的原因。

用Android Motinor 把内存dump 出来。

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 oom_monitor_bitmap.png

这张bitmap 大小为85881640,大约为81M。然后看下分辨率

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》

明显被放大了。手机为 NEXUS 6P, 像素密度为XXXHDPI. 也就是说图片被放大了3×3 9倍的大小。图片原来放在了mipmap 目录,作为HDPI的资源处理了, 手机需要XXX 的资源,然后被放大。图片的位置不正确。

把图片放到不同的目录看下效果:

目录效果
drawable图片放大
mipmap图片放大
drawable-nodpi正常
mipmap-nodpi正常
drawable-xxxdpi正常
mipmap-xxxdpi正常

因此应当把背景图片放到 drawable-nodpi

Bitmap heap

关于android bitmap 内存的分配,在Android 3.0 以前,内存被分配在了 Nativie 堆,3.0 以后被分配在了Java 堆。

dalvik.vm.heapstartsize

堆分配的初始大小,调整这个值会影响到应用的流畅性和整体ram消耗。这个值越小,系统ram消耗越慢,但是由于初始值较小,一些较大的应用需要扩张这个堆,从而引发gc和堆调整的策略,会应用反应更慢。相反,这个值越大系统ram消耗越快,但是程序更流畅。

dalvik.vm.heapgrowthlimit

 极限堆大小,dvm heap是可增长的,但是正常情况下dvm heap的大小是不会超过dalvik.vm.heapgrowthlimit的值。如果受控的应用dvm heap size超过该值,则将引发oom。

dalvik.vm.heapsize

使用大堆时,极限堆大小。一旦dalvik heap size超过这个值,直接引发oom。在android开发中,如果要使用大堆,需要在manifest中指定android:largeHeap为true。这样dvm heap最大可达dalvik.vm.heapsize。

[dalvik.vm.heaptargetutilization]: [0.75]

可以设定内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。

mipmap 和 drawable 目录

Google 为App 开发引入AndroiStudio 以后,用来放图片的目录多了一个mipmap 目录。然后关于图片到底是放在drawable 目录还是mipmap 目录就有了争议。

Using a mipmap as the source for your bitmap or drawable is a simple way to provide a quality image and various image scales, which can be particularly useful if you expect your image to be scaled during an animation.

Android 4.2 (API level 17) added support for mipmaps in the Bitmap class—Android swaps the mip images in your Bitmap when you’ve supplied a mipmap source and have enabled setHasMipMap(). Now in Android 4.3, you can enable mipmaps for a BitmapDrawable object as well, by providing a mipmap asset and setting the android:mipMap attribute in a bitmap resource file or by calling hasMipMap().

您应该将所有启动器图标都置于 res/mipmap-[density]/ 文件夹而非 drawable/ 文件夹内,以确保启动器应用使用最佳分辨率图标。 如需了解有关使用 mipmap 文件夹的详细信息,请参阅管理项目概览。

从这段话来看,是App Icon 使用mipmap 目录。其他资源继续使用drawable 目录。

在StackOver 上有一个解释的比较好的答案:

mipmap-drawables-for-icons

大概意思是 Luncher APK 在显示ICON 时选择高分辨率的图片。有时候为了减小APK 包的大小,APK 就只包含一种分辨率的图片,这时Luncher 会放大图片,图片就会模糊了。

资源文件还是放在drawable 目录。

BAT的APP

然后看下各大厂的App 是怎么放资源文件的。这几个超级APP都没有使用mipmap 文件夹。就是App 图标也放在了drawable 目录。

鹅厂的微信:

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 weixin.png

微信的背景图只放在了drawable 和drawable-hdpi 目录。 在hdpi 目录单独放可能是为了在低端机上适配,节约内存。 以ba.jpg 为例, 320×480 大小,解压后为600K, 在XXXHDPI 手机上放大后为5.4M。

MBC02T6468G8WN:res louie.wang$ find . -name b*.jpg
./drawable/ba.jpg
./drawable/bb.jpg
./drawable/bd.jpg
./drawable/be.jpg
./drawable-hdpi-v4/bb.jpg
./drawable-hdpi-v4/bd.jpg
./drawable-hdpi-v4/be.jpg

./drawable/ba.jpg: JPEG image data, baseline, precision 8, 320x480, frames 3

鹅厂的QQ:

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 QQ.png

QQ 应该使用了插件开发,目录比较简单。手机QQ 在drawable 目录下放了很多背景图,图片都不是太大,但是在高分辨率手机上肯定有内存的占用问题。而且QQ没有提供 XXXHDPI 的资源。

手淘:

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 taobao.png

手淘也采用了插件开发,整个目录清爽了很多。和QQ一样,手淘在drawable 目录放了很多背景图片。没有根据不同的分辨率单独出图。 值得注意的是手淘有一个 drawable-anydpi-v21 目录

ls drawable-anydpi-v21/
design_ic_visibility_off.xml

MBC02T6468G8WN:res louie.wang$ find . -name design_ic_visibility_off.*
./drawable-anydpi-v21/design_ic_visibility_off.xml
./drawable-mdpi-v4/design_ic_visibility_off.png
./drawable-xhdpi-v4/design_ic_visibility_off.png
./drawable-xxhdpi-v4/design_ic_visibility_off.png
./drawable-xxxhdpi-v4/design_ic_visibility_off.png

手机百度:

《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》
《Android 图片资源的目录 -- 一个背景图片引发的内存 OOM》 baidu.png

手机百度 的drawable 目录也放了20 张左右的背景图片。但是手机百度有一个 drawable-nodpi
目录。

ls drawable-nodpi-v4/
back_btn.png   bt_white.9.png    city_search_bg.9.png       titlebar_bg.9.png
bt_grey.9.png  bt_white_p.9.png  groupon_titlebar_bg.9.png  union_list_bg_middle.9.png

根据Google 的指引,

screens_support
nodpi 适用于所有密度的资源。这些是密度独立的资源。不管当前屏幕的密度如何,系统都不会 缩放以此限定符标记的资源。

SVG 图片

在手机淘宝的res 目录下有一个 drawable-anydpi-v21 目录,用来放在矢量图片的。

Google 关于svg 和anydpi的解释:

anydpi:此限定符适合所有屏幕密度,其优先级高于其他限定符。 这对于矢量可绘制对象很有用。 此项为 API 级别 21 中新增配置

Android 4.4(API 级别 20)及更低版本不支持矢量图。如果最低 API 级别设置为上述 API 级别之一,则在使用 Vector Asset Studio 时您有两个选择:生成便携式网络图形 (PNG) 文件(默认)或使用支持库。为实现向后兼容性,Vector Asset Studio 会生成矢量图的光栅图像。矢量和光栅图一起打包到 APK 中。您可以在 Java 代码中以 Drawable 的形式引用矢量图,或在 XML 代码中以 @drawable 的形式引用矢量图;当您的应用运行时,对应的矢量或光栅图像会自动显示,具体取决于 API 级别。

在淘宝的的drawable-anydpi 放了一张design_ic_visibility_off.xml 图片,在drawable-* 目录下都有对应的png 图片。这是Android Studio 自动生成的。

进入自己的工程中找一张SVG 图片看下,可以看到把SVG图片放在了drawable 目录,然后AS 自动的从drawable 目录中把SVG图片分离出来,放在了 drawable-anydpi-v21 目录,同时在对应的drawable-**hdpi 目录下生成png 图片。

MBC02T6468G8WN:AndroidSample louie.wang$ find . -name ic_user.*
./app/build/generated/res/pngs/debug/drawable-anydpi-v21/ic_user.xml
./app/build/generated/res/pngs/debug/drawable-hdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-ldpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-mdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xhdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xxhdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xxxhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-anydpi-v21/ic_user.xml
./app/build/intermediates/res/merged/debug/drawable-hdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-ldpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-mdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xxhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xxxhdpi/ic_user.png
./app/src/main/res/drawable/ic_user.xml

同样把图片放到 drawable-anydpi 目录

MBC02T6468G8WN:AndroidSample louie.wang$ find . -name ic_user.*
./app/build/generated/res/pngs/debug/drawable-anydpi-v21/ic_user.xml
./app/build/generated/res/pngs/debug/drawable-hdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-ldpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-mdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xhdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xxhdpi/ic_user.png
./app/build/generated/res/pngs/debug/drawable-xxxhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-anydpi-v21/ic_user.xml
./app/build/intermediates/res/merged/debug/drawable-hdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-ldpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-mdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xxhdpi/ic_user.png
./app/build/intermediates/res/merged/debug/drawable-xxxhdpi/ic_user.png
./app/src/main/res/drawable-anydpi/ic_user.xml

可以得出如下结论:

  1. App icon 放在mipmap-XXX 目录。放在drawable-XXX 目录也可以,只是某些情况下图片没那么清晰。
  2. 背景图片资源 放在 drawable-nodpi 目录。这样避免自动放大图片。
  3. 自定义的 drawable XML 放在 drawable 目录
  4. SVG 文件可以放在drawable-anydpi 目录,根据Google Doc, anydpi 后缀是特备为矢量图准备的。
    原文作者:赤兔欢
    原文地址: https://www.jianshu.com/p/2f02e98e30a1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞