在開發中應用突然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左右。好崩潰。

看了下這個Activity有一個背景,背景為png圖片。注釋掉這個背景圖片后,從Android Monitor 上看內存的曲線平滑了。原因就是這張圖片了。但是一張圖片也不能吃掉60M的內存。
res/mipmap/pc_car.png: PNG image data, 1440 x 1217, 8-bit/color RGBA, non-interlaced
分辨率大小為 1440x1217, 32為像素,解壓后大小應該為6M, 還是不能解釋上面的原因。
用Android Motinor 把內存dump 出來。

這張bitmap 大小為85881640,大約為81M。然后看下分辨率

明顯被放大了。手機為 NEXUS 6P, 像素密度為XXXHDPI. 也就是說圖片被放大了3x3 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 上有一個解釋的比較好的答案:
大概意思是 Luncher APK 在顯示ICON 時選擇高分辨率的圖片。有時候為了減小APK 包的大小,APK 就只包含一種分辨率的圖片,這時Luncher 會放大圖片,圖片就會模糊了。
資源文件還是放在drawable 目錄。
BAT的APP
然后看下各大廠的App 是怎么放資源文件的。這幾個超級APP都沒有使用mipmap 文件夾。就是App 圖標也放在了drawable 目錄。
鵝廠的微信:

微信的背景圖只放在了drawable 和drawable-hdpi 目錄。 在hdpi 目錄單獨放可能是為了在低端機上適配,節約內存。 以ba.jpg 為例, 320x480 大小,解壓后為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:

QQ 應該使用了插件開發,目錄比較簡單。手機QQ 在drawable 目錄下放了很多背景圖,圖片都不是太大,但是在高分辨率手機上肯定有內存的占用問題。而且QQ沒有提供 XXXHDPI 的資源。
手淘:

手淘也采用了插件開發,整個目錄清爽了很多。和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
手機百度:

手機百度 的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
可以得出如下結論:
- App icon 放在mipmap-XXX 目錄。放在drawable-XXX 目錄也可以,只是某些情況下圖片沒那么清晰。
- 背景圖片資源 放在 drawable-nodpi 目錄。這樣避免自動放大圖片。
- 自定義的 drawable XML 放在 drawable 目錄
- SVG 文件可以放在drawable-anydpi 目錄,根據Google Doc, anydpi 后綴是特備為矢量圖準備的。