Android Performance

Android Code Memory Optimization Suggestions - Android (Official)

Word count: 1.4kReading time: 8 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

To ensure the Garbage Collector (GC) can properly release memory, it’s crucial to avoid memory leaks (often caused by global or static member variables holding object references) and to release references when they are no longer needed. For most apps, the GC handles the rest: if an object is no longer reachable, its memory is reclaimed.

High-performance software requires proactive memory management throughout the development lifecycle. Android provides several specific guidelines and techniques to help developers achieve excellent memory performance.

Introduction

This content is derived from the Managing Your App’s Memory section of the official Android developer documentation, specifically How Your App Should Manage Memory. As an Android developer, you must consider memory at every stage of coding.

1. Use Services Sparingly

If your app needs a Service for background tasks, ensure it only runs while the task is active. Be cautious about Service stop failures, as they lead to memory leaks.

When a Service starts, the system tends to keep its parent process in memory, making that process very “expensive.” This reduces the number of other processes the system can cache in the LRU (Least Recently Used) list, degrading overall system performance and potentially causing crashes under high memory pressure.

To manage Service lifecycles effectively, Android recommends using IntentService. It automatically stops once its background task is finished, significantly reducing the risk of memory leaks.

Keeping a Service running when it’s idle is a major performance “anti-pattern.” Not only does it slow down the device, but it also increases the likelihood of users uninstalling your app once they notice its resource consumption.

2. Release Memory When UI is Hidden

When a user switches to another app and your UI is no longer visible, you should release all UI-bound resources. This increases the system’s capacity to cache background processes, improving the overall user experience.

To detect when your UI is hidden, override the onTrimMemory() method in your Activity and listen for the TRIM_MEMORY_UI_HIDDEN level:

1
2
3
4
5
6
7
8
9
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
switch (level) {
case TRIM_MEMORY_UI_HIDDEN:
// Release UI resources here
break;
}
}

Note that TRIM_MEMORY_UI_HIDDEN is triggered only when all UI components of your app are hidden. This differs from onStop(), which is called when a single Activity is no longer visible (e.g., when the user navigates to another Activity within your app). Use onStop() to release Activity-specific resources (like network connections or receivers), but wait for TRIM_MEMORY_UI_HIDDEN to release global UI resources to avoid unnecessary reloading when navigating within the app.

3. Release Memory During Pressure

The onTrimMemory() method provides several callbacks during various stages of memory pressure. You should adjust your resource release strategy based on these levels:

3.1 While the App is Running

  • TRIM_MEMORY_RUNNING_MODERATE: The device is beginning to run low on memory. Your app is running and won’t be killed, but the system is starting to clear LRU cache processes.
  • TRIM_MEMORY_RUNNING_LOW: Memory is quite low. Release unnecessary resources to improve both system and app performance.
  • TRIM_MEMORY_RUNNING_CRITICAL: Memory is extremely low. The system has already killed most cached processes. You must release all non-essential resources to prevent the system from killing active services or your app.

3.2 While the App is Cached

  • TRIM_MEMORY_BACKGROUND: Memory is low, and your app is in the LRU cache. It’s safe but should release easy-to-rebuild resources to help the system and stay in the cache longer.
  • TRIM_MEMORY_MODERATE: Memory is very low, and your app is in the middle of the LRU list. It’s at risk of being killed.
  • TRIM_MEMORY_COMPLETE: Memory is critical, and your app is at the end of the LRU list. It will be among the first to be killed. Release everything possible.

For API levels below 14, use onLowMemory(), which is roughly equivalent to TRIM_MEMORY_COMPLETE.

Note: While the system usually kills processes from the bottom of the LRU list up, it may target large-memory processes first to reclaim space quickly. Keeping your memory footprint small helps you stay in the cache longer.

4. Check Your Memory Limits

Different Android devices have different RAM capacities and heap limits. Use getMemoryClass() to find your app’s available heap size. Exceeding this triggers an OutOfMemoryError.

In special cases, you can request a larger heap by setting largeHeap=true in your manifest’s <application> tag. Check the resulting size with getLargeMemoryClass().

Do not use largeHeap just because you’ve run out of memory. It is intended for apps that inherently require a huge footprint (e.g., high-res photo editors). A larger heap increases GC duration and negatively impacts system performance during task switching. Also, on some devices, the “large” heap is no different from the standard heap.

5. Avoid Bitmap Waste

Never load a Bitmap at a resolution higher than needed for its display container. A high-res image in a small ImageView provides no visual benefit but wastes significant RAM. Remember: the memory footprint of a Bitmap is calculated by pixels, not the file size on disk. An ARGB_8888 image of 1500x1000 pixels uses 1500 * 1000 * 4 bytes = 5.7MB, regardless of whether the JPG file is only 100KB.

6. Use Optimized Data Collections

Use Android-specific containers like SparseArray, SparseBooleanArray, and LongSparseArray. HashMap is more memory-intensive as it requires an extra entry object for every mapping. SparseArray avoids auto-boxing of keys and values, saving further resources.

7. Be Aware of Memory Overheads

Different coding styles have different memory costs:

  • Enums: Can consume more than 2x the memory of static constants. Avoid them in memory-sensitive Android code.
  • Classes: Every Java class (including inner/anonymous) takes roughly 500 bytes.
  • Instances: Every instance costs 12-16 bytes of overhead.
  • HashMaps: Using a primitive key like an int still incurs the overhead of an object wrap (roughly 32 bytes vs. 4 bytes). Use specialized collections instead.

8. Be Careful with Abstractions

While abstraction is good for OOP design and maintainability, it carries a memory cost on Android due to extra code mapping and reduced execution efficiency. Use abstraction judiciously—only when the architectural benefits outweigh the overhead.

9. Use Nano ProtoBufs for Serialized Data

Protocol Buffers are lightweight and language-agnostic. For Android clients, always use the nano version to minimize generated code, RAM usage, and APK size, and to avoid hitting DEX method limits.

10. Avoid Dependency Injection Frameworks

Frameworks like Guice or RoboGuice can simplify code with annotations like @InjectView, but they often involve long initialization phases and scan for annotations using reflection. This loads many unnecessary objects into memory. Manually calling findViewById() is often more efficient.

11. Be Cautious with External Libraries

Many external libraries aren’t optimized for mobile environments. Carefully evaluate any library before integration. For example, avoid importing a large library for only one or two features. Multiple libraries might also bring redundant implementations of the same functionality (e.g., two different ProtoBuf versions), bloating your APK and RAM usage.

12. Optimize Overall Performance

Refer to Google’s Best Practices for Performance. Also, optimize your UI with the layout optimization guidelines and pay attention to Lint warnings.

13. Use ProGuard

ProGuard shrinks, optimizes, and obfuscates your code by removing unused paths and renaming classes. This results in smaller APKs and less RAM needed for mapped code.

14. Use zipalign

Always run zipalign on your final APK. Failure to do so prevents resources (like images) from being memory-mapped efficiently, significantly increasing RAM usage. Google Play requires zipalign.

15. Analyze RAM Usage

Once your app is stable, analyze its memory lifecycle. See Investigating Your RAM Usage.

16. Use Multiple Processes

For apps that must perform heavy background work (like a music player), consider splitting the app into components running in different processes. This allows the UI process to be killed and reclaimed while the background Service process continues to run. Use the android:process attribute in your manifest:

1
2
<service android:name=".PlaybackService"
android:process=":background" />

Use this technique cautiously, as it can increase total memory usage if implemented incorrectly.

About Me && Blog

(Links and introduction)

CATALOG
  1. 1. Introduction
    1. 1.1. 1. Use Services Sparingly
    2. 1.2. 2. Release Memory When UI is Hidden
    3. 1.3. 3. Release Memory During Pressure
      1. 1.3.1. 3.1 While the App is Running
      2. 1.3.2. 3.2 While the App is Cached
    4. 1.4. 4. Check Your Memory Limits
    5. 1.5. 5. Avoid Bitmap Waste
    6. 1.6. 6. Use Optimized Data Collections
    7. 1.7. 7. Be Aware of Memory Overheads
    8. 1.8. 8. Be Careful with Abstractions
    9. 1.9. 9. Use Nano ProtoBufs for Serialized Data
    10. 1.10. 10. Avoid Dependency Injection Frameworks
    11. 1.11. 11. Be Cautious with External Libraries
    12. 1.12. 12. Optimize Overall Performance
    13. 1.13. 13. Use ProGuard
    14. 1.14. 14. Use zipalign
    15. 1.15. 15. Analyze RAM Usage
    16. 1.16. 16. Use Multiple Processes
  • About Me && Blog