Android Performance

Android Code Memory Optimization Suggestions - Java (Official)

Word count: 1.2kReading time: 7 min
2015/07/20
loading

Android Memory Optimization Series:

  1. Android Code Memory Optimization Suggestions - Android (Official)
  2. Android Code Memory Optimization Suggestions - Java (Official)
  3. Android Code Memory Optimization Suggestions - Android Resources
  4. 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 String and you know it will eventually be appended to a StringBuffer, modify your implementation to avoid direct concatenation; instead, use a temporary object or pass the buffer.
  • When extracting Strings from an input dataset, try to return a substring of the original data rather than creating a copy.

A more aggressive approach is to decompose multi-dimensional data into 1D arrays:

  • A set of int values is better than a set of Integer objects. 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 arrays Foo[] and Bar[] 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
2
static int intVal = 42;
static String strVal = "Hello, world!";

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
2
static final int intVal = 42;
static final String strVal = "Hello, world!";

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static class Foo {
int mSplat;
}

Foo[] mArray = ...

public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}

public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;

for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}

public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
  • 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 to one() 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Foo {
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}

private int mValue;

public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}

private void doStuff(int value) {
System.out.println("Value is " + value);
}
}

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
2
3
4
5
6
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}

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)

CATALOG
  1. 1. Introduction
  2. 2. 1) Avoid Creating Unnecessary Objects
  3. 3. 2) Prefer Static Over Virtual
  4. 4. 3) Use Static Final for Constants
  5. 5. 4) Avoid Internal Getters/Setters
  6. 6. 5) Use Enhanced For Loop Syntax
  7. 7. 6) Use Package Access Instead of Private Access for Inner Classes
  8. 8. 7) Avoid Float
  9. 9. 8) Use Library Functions
  10. 10. 9) Use Native Methods Sparingly
  11. 11. 10) Performance Myth: Concrete vs. Interface
  12. 12. 11) Measurement
  • About Me && Blog