Recently I’ve been implementing an animated user interface where the animations are defined in a proprietary file format. When the interface is brought up on Android™, the file gets walked and all the View objects created; when an animation takes place, the definitions in the file are converted into Android key frames. Everything seemed to work well… until I imported a file with what seemed like a harmless optimization.
Several buttons in the user interface incorporated a “glowing” effect, basically by having the glow defined in an image file and animating its alpha transparency. The same image file was in use at several locations on the screen, just scaled to match the button. I decided to cache the Android Drawable, creating just one for each image and attaching it to multiple ImageView objects as necessary. As I loaded the file, the repeated copies of the glow image appeared in several places on the screen. Surely this would be more efficient?
Do Shared Android Drawables Work?
It turns out that actually they don’t, at least, not in my particular case. The results were surprising, and sent me on a wild goose chase all over the code looking for bugs elsewhere. Even though every ImageView used the “scale to fit” setting with
and even though I read the following lines in the Android documentation about Drawables:
if you instantiate two Drawable objects from the same image resource, then change a property … for one of the Drawables, then it will also affect the other. So when dealing with multiple instances of an image resource, instead of directly transforming the Drawable, you should perform a tween animation
I could not even figure out what was going on. The documentation seemed to suggest there was little point in creating more than one; they would all point to the same image in memory. Some of the ImageViews scaled the image incorrectly, either leaving empty space below it or making the image too wide horizontally and clipping to the view box. It wasn’t the animations; as per the documentation they operated on the containing View, not the Drawable. For some reason, the ImageViews weren’t scaling the images the way I had set them to do.
After spending most of the day staring at the emulator and the hierarchy viewer without making any connection, I finally saw it. Even though each ImageView was set to scale to fit, the drawable in each was exactly the same size. In fact, it was only the right size for the last occurrence in the file. A quick look in the relevant Android source code showed me exactly why. When a Drawable is attached to an ImageView, the size and bounds specific to that view are committed to the drawable. When the system later displays this, it is drawn with the required scale and clipping. Obviously, sharing the same drawable would not work; later instances would overwrite the scale and clipping information.
The correct answer was in fact the simpler one; don’t cache the drawables at all. Passing a resource ID to each ImageView – the simplest way to set an image of all – correctly allocates an independent Drawable for each. Don’t over-think it – let the system just do its thing. Another example of premature optimization being the root of all evil, I suppose. There wasn’t a significant performance issue there at all, in any case. Just remember that an Android Drawable is not just “something that can be drawn” but also contains information on how to draw it, and it will be apparent exactly when you need to create separate instances. If for instance your image is identical every place it appears (perhaps multiple copies of the same enemy in a Space Invaders game), then you could share the same Drawable.Android is a trademark of Google Inc. Android robot image by Google (CC BY-SA 3.0)