🛠️ Debugging AOSP System Software Memory Leaks Using LeakCanary

Memory leaks in AOSP system software—whether in privileged apps like Bluetooth or services like SystemUI—can silently degrade device performance and eventually trigger OOM crashes or ANRs. Traditional debugging tools struggle to catch these subtle, long-lived leaks.

In this article, I’ll show you how to instrument LeakCanary into AOSP system components—even services not designed for app-level inspection—and use it to detect memory leaks in system software like Bluetooth.apk, Settings, or even your own custom privileged services.

🧠 Why LeakCanary for System Software?

LeakCanary is primarily designed for Android app developers. But it becomes even more powerful in AOSP system-level debugging:

Catches leaks in framework-facing services
Tracks long-lived Handler threads or leaked Context in privileged apps
Offers real-time leak traces even when no UI is involved

☝️ It’s especially useful for debugging Bluetooth, Telephony, or Connectivity services where services are restarted often or context mismanagement is common.

🔧 Step-by-Step: Integrate LeakCanary into AOSP

Let’s take a system app or service like com.android.bluetooth as our example.

📁 1. Clone and Embed LeakCanary in AOSP
LeakCanary is not prebuilt into AOSP. You need to vendor it yourself.

Clone LeakCanary:

git clone https://github.com/square/leakcanary

Copy its source into your AOSP tree:

cp -r leakcanary/leakcanary-android-core/src/main/java/* 
    packages/apps/Bluetooth/src/com/squareup/leakcanary/

🛠️ 2. Modify Android.bp in Your System App
Update the Android.bp file of your system app:

java_library {
    name: "bluetooth-leakcanary",
    srcs: ["src/**/*.java"],
    static_libs: ["leakcanary-core"],
    sdk_version: "current",
}

In case your module uses platform_compat or system_current, set appropriate sdk_version.

📲 3. Inject LeakCanary in Your Service Process
Let’s assume you are instrumenting AdapterService in the Bluetooth stack:

public class AdapterService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(getApplication());
    }
}

Ensure that the Application class is properly used in the system manifest or override ContextImpl if needed.

🔐 Note: You must have read/write permission to /data/leakcanary/ or use rooted devices/emulators.

🧪 Run, Leak, Repeat: AOSP Bluetooth Example

Here’s a real example from debugging the AOSP Bluetooth stack:

Scenario:
Toggle Bluetooth multiple times. Eventually, you notice increased memory usage.

LeakCanary output:

┬───
│ GC ROOT static android.bluetooth.BluetoothAdapter.sService
│ leaks AdapterService instance
│ ↓ AdapterService.mHandler
│   ↓ Callback retained Context

🔍 Analysis:

sService holds onto an old AdapterService instance.
The Handler inside retains the previous Context.
That context is no longer valid post-service restart.

✅ Fix:
Clear or nullify references in onDestroy():

@Override
public void onDestroy() {
    mHandler.removeCallbacksAndMessages(null);
    sService = null;
    super.onDestroy();
}

📉 Dumping Heap for Offline Analysis

LeakCanary automatically creates heap dumps (.hprof). You can push them to your host:

adb pull /sdcard/leakcanary-<timestamp>.hprof

🧾 Conclusion

Memory leaks in AOSP system software are often hard to spot and harder to debug. LeakCanary can be retrofitted into system components, giving you visibility that otherwise would require JNI dumps or heavy profiling.

Whether you’re optimizing Bluetooth, investigating SystemUI bloat, or improving your custom HAL-based service—LeakCanary gives you the tools to debug like a pro.

🔗 Resources

LeakCanary GitHub
AOSP Source Tree Setup
Eclipse MAT

Leave a Reply