Android Memory Optimization Series:
- Android Code Memory Optimization Suggestions - Android (Official)
- Android Code Memory Optimization Suggestions - Java (Official)
- Android Code Memory Optimization Suggestions - Android Resources
- Android Code Memory Optimization Suggestions - OnTrimMemory
This article introduces micro-optimization techniques that, when combined, contribute to the overall performance of an app, although they don’t provide massive gains compared to choosing the right algorithms and data structures. You should incorporate these tips into your coding habits to improve efficiency.
This content is based on the Google Official Training for Performance Optimization, specifically focusing on high-performance Android code. I recommend all Android developers read these guidelines and apply these principles in their work.
Original: http://developer.android.com/training/articles/perf-tips.html
Introduction
Generally, efficient code follows two rules:
- 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 face when micro-optimizing an Android app is that your app is guaranteed to run on multiple types of hardware. Different VM versions running on different processors run at different speeds. It’s not even as simple as saying “Device X is faster/slower than Device Y because of factor F.” Performance doesn’t scale linearly across devices. For example, the emulator often has no comparable performance to physical hardware. There are also huge differences between devices with and without a JIT (Just-In-Time) compiler.
Performance is affected by the CPU, RAM, OS version, and many other factors. To ensure your code runs well across the ecosystem, you must maximize its efficiency.
1) Avoid Creating Unnecessary Objects
While the Garbage Collector (GC) reclaims unused objects, allocating and reclaiming memory both cost resources. Avoid creating transient objects when possible:
- If you need to return a
Stringand you know it will eventually be appended to aStringBuffer, modify your implementation to avoid direct concatenation; instead, use a temporary object or pass the buffer. - When extracting
Stringsfrom an input dataset, try to return asubstringof the original data rather than creating a copy.
A more aggressive approach is to decompose multi-dimensional data into 1D arrays:
- A set of
intvalues is better than a set ofIntegerobjects. Two 1D arrays are more efficient than one 2D array. This applies to all primitive types. - If you need an array to store
(Foo, Bar)objects, using two arraysFoo[]andBar[]is more efficient than an array of(Foo, Bar)objects. (Balance this with API design—compromises are sometimes necessary for readability).
Fewer objects mean fewer GCs, which directly improves user experience.
2) Prefer Static Over Virtual
If you don’t need to access an object’s fields, make the method static. Method calls will be 15%-20% faster. It’s also a good habit because the static modifier tells the caller that the method won’t change the object’s state.
3) Use Static Final for Constants
Consider this declaration:
1 | static int intVal = 42; |
The compiler generates a <clinit> method to initialize these values when the class is first used. Accessing them later requires a lookup. Use static final to improve performance:
1 | static final int intVal = 42; |
This avoids the extra lookup. Always use static final for constants.
4) Avoid Internal Getters/Setters
In native languages like C++, it’s common to use getters (i = getCount()) instead of direct field access (i = mCount). This is an excellent habit in C++, C#, and Java because compilers often inline these accesses.
However, on Android, this is a bad practice. Virtual method calls are more expensive than direct field access. Recommendation: Use getters/setters for public APIs, but use direct field access within the class itself.
Without a JIT, direct access is 3x faster than a getter. With a JIT, it’s 7x faster. Note that if you use ProGuard, it can inline these for you, giving you the same effect.
5) Use Enhanced For Loop Syntax
Compare these three loops:
1 | static class Foo { |
zero()is the slowest because the JIT cannot optimize it well.one()is slightly faster.two()is the fastest on non-JIT devices and roughly equal toone()with a JIT. It uses the “for-each” loop.
Use for-each for arrays, but for ArrayList, the manual one() style loop is preferred.
6) Use Package Access Instead of Private Access for Inner Classes
Consider this code:
1 | public class Foo { |
The issue here is that the VM treats Foo and Foo$Inner as separate classes, even though Java allows inner classes to access private members of the outer class. To bridge this, the compiler generates “synthetic” methods:
1 | /*package*/ static int Foo.access$100(Foo foo) { |
Every time the inner class accesses mValue or doStuff(), it calls these static methods. As mentioned earlier, accessor methods are slower than direct access. If you are in a performance-critical “hotspot,” make these fields and methods package-private (default visibility) instead of private. Note that this exposes them to other classes in the same package, so it shouldn’t be done in public APIs.
7) Avoid Float
On Android, float data access is roughly half the speed of int. Prefer int when possible.
8) Use Library Functions
Always prefer built-in functions like System.arraycopy(). They are implemented in native code and are up to 9x faster than manual loops.
Tip: Also see Josh Bloch’s Effective Java, Item 47.
9) Use Native Methods Sparingly
Native code (via JNI) is not inherently faster for simple tasks. Only use JNI if you are migrating existing native code or have a compute-heavy task. Read JNI Tips for more.
10) Performance Myth: Concrete vs. Interface
Before JIT, using concrete types was faster than interfaces (e.g., HashMap vs Map). The speed difference was rumored to be 2x, but is actually about 6%. With a JIT, there is virtually no difference.
11) Measurement
The data in this doc reflects real-world Android performance. You can use Traceview for profiling, but keep in mind that Traceview measurements aren’t JIT-optimized, so actual production performance is usually slightly better.
For more on profiling and debugging:
About Me && Blog
(Links and introduction)