Android Performance

Android Code Memory Optimization Suggestions - Android Resources

Word count: 966Reading time: 6 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 focuses on common memory leak scenarios in Android application development. Having a baseline understanding of memory management before writing code leads to much more robust applications. This post starts with resource usage in Android and covers optimizations for Bitmaps, database queries, Nine-Patch assets, overdraw, and more.

Android Resource Optimization

1. Bitmap Optimization

Most memory issues in Android trace back to Bitmaps. If you inspect a heap dump with MAT (Memory Analyzer Tool), the largest memory consumers are almost always byte[] arrays representing Bitmaps. Google has a dedicated series on this: Displaying Bitmaps Efficiently. Before optimizing, ensure you are following these best practices.

A Bitmap usually stays in memory for two reasons:

  • The developer didn’t explicitly release it after use.
  • The Bitmap is still being referenced, preventing garbage collection.

1.1 Manually Releasing Bitmap Resources

Once you are certain a Bitmap is no longer needed, call recycle() to release its native memory. While keeping it might speed up future resumes, its large footprint might cause your app to be killed in the background, which is counterproductive.

1
2
3
4
if(bitmap != null && !bitmap.isRecycled()){  
bitmap.recycle();
bitmap = null;
}

From Bitmap.java: recycle() marks the bitmap as “dead,” clearing references to pixel data. While GC will eventually reclaim this, manual recycling is safer because GC is non-deterministic and can cause lag if it has to reclaim many large objects at once.

1.2 Releasing ImageView Images

If you set images via XML (src) or methods like setImageResource, you can use the following to manually clear the resource:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void recycleImageViewBitMap(ImageView imageView) {
if (imageView != null) {
BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
rceycleBitmapDrawable(bd);
}
}

private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
if (bitmapDrawable != null) {
Bitmap bitmap = bitmapDrawable.getBitmap();
rceycleBitmap(bitmap);
}
bitmapDrawable = null;
}

private static void rceycleBitmap(Bitmap bitmap) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}

1.3 Releasing ImageView Backgrounds

If your ImageView has a background:

1
2
3
4
5
6
public static void recycleBackgroundBitMap(ImageView view) {
if (view != null) {
BitmapDrawable bd = (BitmapDrawable) view.getBackground();
rceycleBitmapDrawable(bd);
}
}

1.4 Prefer Nine-Patch Over Standard PNGs

As screen resolutions increase, so does the memory cost of loading images. Avoid using large, static PNGs for backgrounds; use Nine-Patch (.9.png) assets instead. These are small PNGs with defined stretchable areas, allowing them to scale without distortion and adapt to multiple screen sizes. Android Studio includes a built-in tool to convert PNGs to Nine-Patch assets.

Original vs Stretched Nine-Patch

1.5 Compress Large Images Before Loading

Often, the source image (e.g., from a camera) has a much higher resolution than the screen. Loading a full-res image into a small ImageView wastes memory and causes jank during scrolling.

Google recommends two key techniques:

  • Decode the dimensions (inJustDecodeBounds) before loading.
  • Load a scaled-down version (inSampleSize) that matches your UI size.

Reference: Loading Large Bitmaps Efficiently.

2. Unclosed Database Cursors

Failing to close a Cursor after a query is a common error. While small result sets might not cause immediate issues, cumulative unclosed cursors will lead to leaks and instability over time.

Incorrect:

1
2
Cursor cursor = getContentResolver().query(uri ...);
if (cursor.moveToNext()) { ... }

Correct:

1
2
3
4
5
6
7
8
9
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri ...);
if (cursor != null && cursor.moveToNext()) { ... }
} finally {
if (cursor != null) {
try { cursor.close(); } catch (Exception e) { /* ignore */ }
}
}

3. Not Recycling convertView in Adapters

In BaseAdapter.getView(int position, View convertView, ViewGroup parent), the convertView parameter allows you to reuse view objects that have scrolled off-screen. If you instantiate a new View every time instead of checking if convertView is null, you waste resources and cause meaningful memory inflation.

ListView Reuse Mechanism

Incorrect:

1
2
3
public View getView(int position, View convertView, ViewGroup parent) {
return new Xxx(...);
}

Correct:

1
2
3
4
5
public View getView(int position, View convertView, ViewGroup parent) {
View view = (convertView != null) ? convertView : new Xxx(...);
populate(view, getItem(position));
return view;
}

4. Releasing Object References

Objects are not collected as long as they are reachable from a GC Root.

Scenario A: Handlers and Runnables
If a MainActivity has a member variable obj used inside a Runnable posted to a Handler, obj will stay in memory as long as MainActivity exists, even if the thread is done. Explicitly nulling the reference helps:

1
2
3
4
5
final Object o = obj;
obj = null;
mHandler.post(new Runnable() {
public void run() { useObj(o); }
});

Scenario B: Listeners and Subscriptions
If a short-lived Activity (like a LockScreen) registers a listener (like PhoneStateListener) with a long-lived System Service (TelephonyManager), forgetting to unregister the listener in onDestroy will leak the entire Activity.

5. Releasing Resources in Lifecycle Callbacks

Most resource cleanup should happen in onPause(), onStop(), or onDestroy(). Check the official Activity lifecycle documentation to determine exactly when to release which resources (e.g., stopping animations, closing camera connections, or unregistering receivers).

6. Eliminating Overdraw

Overdraw happens when you paint a pixel multiple times in a single frame. For example, a TextView with a background paints the background pixel first, then the text pixel over it. Excessive overdraw strains the GPU’s memory bandwidth and impacts performance. Minimize overdraw by removing redundant backgrounds from parent layouts.

7. Using System Resources

Android provides many built-in strings, drawables, and layouts. Using @android:id/list or @android:color/white instead of duplicating them in your project reduces APK size and RAM usage.

8. Using Memory Profiling Tools

You can’t guarantee a perfect application on the first attempt. Make memory checks (using tools like Lint, MAT, or Memory Profiler) part of every development phase.

About Me && Blog

(Links and introduction)

CATALOG
  1. 1. Android Resource Optimization
    1. 1.1. 1. Bitmap Optimization
      1. 1.1.1. 1.1 Manually Releasing Bitmap Resources
      2. 1.1.2. 1.2 Releasing ImageView Images
      3. 1.1.3. 1.3 Releasing ImageView Backgrounds
      4. 1.1.4. 1.4 Prefer Nine-Patch Over Standard PNGs
      5. 1.1.5. 1.5 Compress Large Images Before Loading
    2. 1.2. 2. Unclosed Database Cursors
    3. 1.3. 3. Not Recycling convertView in Adapters
    4. 1.4. 4. Releasing Object References
    5. 1.5. 5. Releasing Resources in Lifecycle Callbacks
    6. 1.6. 6. Eliminating Overdraw
    7. 1.7. 7. Using System Resources
    8. 1.8. 8. Using Memory Profiling Tools
  2. 2. About Me && Blog