I write a fun dice puzzle game with my friends called, Lock n Roll, but a recent face lift giving it higher resolution graphics resulted in some crash reports! Make sure your app doesn’t run into the same with the quick prep step below.
Here is an example of an asset that changed:
Basically an upscale, but done as part of the release, so strong filtering could be applied. This particular image is stretched across the entire screen as the window background while the game is loading to act as a splash screen. I was providing only one large version in the drawable-nodpi resources folder and expecting all phones to scale it as needed at runtime. Other types of images were provided only in drawable-xhdpi and getting scaled down for lower densities.
Figuring Out the Problem
Some crashes were reported remotely that look like this:
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
I wasn’t able to get them to occur on my own devices or emulators, didn’t see any memory leaks or strange allocations in DDMS analysis, and they all started after I bumped up the resolution of my graphics assets in the app. The one reproducible clue was that the 2.3.3 MDPI emulator I often test apps on was much slower than usual, even for the Android emulator.
The first tablet OS emulator images back when they were released were similarly slow, and Google reps said it was due to the amount of pixels being dealt with, so this really should have clued me in before release. Unfortunately, the memory given to your process in Android is tied to the screen size/density, so the phones with the least memory were having to do the most on the fly graphics scaling.
The tools I used to fix this are Cygwin a POSIX command line for Windows, with the ImageMagick image conversion command installed from the setup program. Converting a large facebook graphic from the drawable-nodpi folder to the drawable-xhdpi folder looks like this:
convert drawable-nodpi/image_facebook_logo.png -resize 96x96 drawable-xhdpi/image_facebook_logo.png
Converting everything from drawable-xhdpi down to the other screen density folders looks like this:
for img in `ls *.png *.jpg`; do convert $img -resize 75% ../drawable-hdpi/$img ; done ;
for img in `ls *.png *.jpg`; do convert $img -resize 50% ../drawable-mdpi/$img ; done ;
for img in `ls *.png *.jpg`; do convert $img -resize 37.5% ../drawable-ldpi/$img ; done ;
Boom! Done. Do watch out for *.9.png images, however. These have a 1 pixel border indicating the stretchable and content padding parts of the image, so can’t be scaled so easily. The borders have to stay 1 pixel.
Fortunately, they also tend to look good with very few pixels so aren’t often very large anyway. The stretchable areas are usually the ones with the least detail, and more detailed areas like the corners of buttons are less affected by size changes thanks to that.
With the above changes devices should be doing much less scaling from now on. The extra slow emulator is fixed and my crash reporting dashboard looks better at least!
My Facebook image used to be 140x140 pixels large, but was always used at size 48dp (which is scaled to 48 pixels on medium density class devices, twice that at xhdpi, etc.). So it was always being scaled and now it is never being scaled except on tvdpi anf xxhdpi devices.
It’s rough on the developer to have to maintain many more graphics. Every little change to the size they are used or to the graphics requires tweaking many files. Maybe someday the app will get custom tweaked graphics for each resolution from a nice artist, or I’ll build an assets packaging pipeline.
Such a pipeline would always pre-process an app before publishing to fill in the missing resizes, allowing the source tree to retain only the source image. This sort of extensive preprocessing is common in game engines and development environments. There it is often beneficial to have the images in a format the GPU can use and sometimes even all stuck together into larger atlas images with many images inside.
I also dislike how much larger the end file size of the app is. I’ve watched users wait for apps to download and they are just as likely to forget about the app as they are to forget about slow loading web sites, which they do in droves. This is especially bad when demoing devices to people at events where connections are overloaded. Hopefully Google will eventually tweak Android Market to remove assets not used by the device downloading the apps.
Hope this helps your own app development! Check out Lock n Roll on Google Play if you need a fun break. :)