This document primarily covers micro-optimizations that can improve overall app performance when combined, but it’s unlikely that these changes will result in dramatic performance effects. Choosing the right algorithms and data structures should always be your priority, but is outside the scope of this document. You should use the tips in this document as general coding practices that you can incorporate into your habits for general code efficiency.
There are two basic rules for writing efficient code:
- Don’t do work that you don’t need to do.
- Don’t allocate memory if you can avoid it.
One of the trickiest problems you’ll face when micro-optimizing an Android app is that your app is certain to be running on multiple types of hardware. Different versions of the VM running on different processors running at different speeds. It’s not even generally the case that you can simply say “device X is a factor F faster/slower than device Y”, and scale your results from one device to others. In particular, measurement on the emulator tells you very little about performance on any device. There are also huge differences between devices with and without a JIT: the best code for a device with a JIT is not always the best code for a device without.
To ensure your app performs well across a wide variety of devices, ensure your code is efficient at all levels and aggressively optimize your performance.
Avoid Creating Unnecessary Objects
Object creation is never free. A generational garbage collector with per-thread allocation pools for temporary objects can make allocation cheaper, but allocating memory is always more expensive than not allocating memory.
As you allocate more objects in your app, you will force a periodic garbage collection, creating little “hiccups” in the user experience. The concurrent garbage collector introduced in Android 2.3 helps, but unnecessary work should always be avoided.
Thus, you should avoid creating object instances you don’t need to. Some examples of things that can help:
- If you have a method returning a string, and you know that its result will always be appended to a StringBuffer anyway, change your signature and implementation so that the function does the append directly, instead of creating a short-lived temporary object.
- When extracting strings from a set of input data, try to return a substring of the original data, instead of creating a copy. You will create a new String object, but it will share the char with the data. (The trade-off being that if you’re only using a small part of the original input, you’ll be keeping it all around in memory anyway if you go this route.)
A somewhat more radical idea is to slice up multidimensional arrays into parallel single one-dimension arrays:
- An array of ints is a much better than an array of Integer objects, but this also generalizes to the fact that two parallel arrays of ints are also a lot more efficient than an array of (int,int) objects. The same goes for any combination of primitive types.
- If you need to implement a container that stores tuples of (Foo,Bar) objects, try to remember that two parallel Foo and Bar arrays are generally much better than a single array of custom (Foo,Bar) objects. (The exception to this, of course, is when you’re designing an API for other code to access. In those cases, it’s usually better to make a small compromise to the speed in order to achieve a good API design. But in your own internal code, you should try and be as efficient as possible.)
Generally speaking, avoid creating short-term temporary objects if you can. Fewer objects created mean less-frequent garbage collection, which has a direct impact on user experience