Codey
Codey

Reputation: 460

Does LeakCanary has callback?

How can I get the LeakCanary log or any kind data about the leak? Does LeakCanary has any kind of Callback that we can use to get the "leak data" to do something with it in the time it's happend? I want to send the data to my FireBase or some other DB.

I searched in the documentation, but didnt found somthing about it.

Thanks to all

Upvotes: 1

Views: 409

Answers (2)

Dmytro Karataiev
Dmytro Karataiev

Reputation: 1304

My solution is a little bit different (based on the sample from square: https://square.github.io/leakcanary/recipes/#uploading-to-bugsnag), but the idea is the same. We use Sentry and Firebase via Timber to log memory leaks. I find sentry logging a bit more convenient since it shows the exact steps that a I took before getting a memory leak (screens opened, background/foreground).

/**
 * Helper class to record leak canary memory leak traces on Timber.
 */
class LeakCanaryService : OnHeapAnalyzedListener {

    private val defaultLeakListener = DefaultOnHeapAnalyzedListener.create()

    override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
        // Delegate to default behavior (notification and saving result)
        defaultLeakListener.onHeapAnalyzed(heapAnalysis)

        when (heapAnalysis) {
            is HeapAnalysisSuccess -> {
                val allLeakTraces = heapAnalysis
                    .allLeaks
                    .toList()
                    .flatMap { leak ->
                        leak.leakTraces.map { leakTrace -> leak to leakTrace }
                    }

                allLeakTraces.forEach { (leak, leakTrace: LeakTrace) ->
                    val exception = MemoryLeakReportingException(leak.shortDescription)
                    Timber.e(exception, "Memory leak recorded: ${exception.message}\n$leakTrace")
                }

            }
            is HeapAnalysisFailure -> {
                // Please file any reported failure to
                // https://github.com/square/leakcanary/issues
                Timber.e(
                    heapAnalysis.exception,
                    "Memory leak analysis failed: ${heapAnalysis.exception.message}"
                )

            }
        }
    }

    class MemoryLeakReportingException(message: String) : RuntimeException(message)

}

You initialize this class in the App class:

LeakCanary.config = LeakCanary.config.copy(
    onHeapAnalyzedListener = LeakCanaryService()
)

Upvotes: 0

pantos27
pantos27

Reputation: 629

TLDR; you need to extend DisplayLeakService

https://github.com/square/leakcanary/wiki/Customizing-LeakCanary#uploading-to-a-server

You can change the default behavior to upload the leak trace and heap dump to a server of your choosing.

Create your own AbstractAnalysisResultService. The easiest way is to extend DisplayLeakService in your debug sources:

public class LeakUploadService extends DisplayLeakService {
  @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
      return;
    }
    if (result.leakFound) {
      uploadLeakToServer(result, leakInfo);
    }
  }

  private void uploadLeakToServer(AnalysisResult result, String leakInfo) {
    // TODO Upload result to server
  }
}

You can translate leak traces to fake exceptions with AnalysisResult.leakTraceAsFakeException() and upload them to a crash reporting backend. Here's how you could do it with Bugsnag:

public class LeakUploadService extends DisplayLeakService {

  @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
      return;
    }
    if (result.leakFound) {
      uploadLeakToServer(result, leakInfo);
    }
  }

  private void uploadLeakToServer(AnalysisResult result, String leakInfo) {
    Client bugsnagClient = new Client(getApplication(), "YOUR_BUGSNAG_API_KEY", false);
    bugsnagClient.setSendThreads(false);
    bugsnagClient.beforeNotify(error -> {
      // Bugsnag does smart grouping of exceptions, which we don't want for leak traces.
      // So instead we rely on the SHA-1 of the stacktrace, which has a low risk of collision.
      String stackTraceString = Logs.getStackTraceString(error.getException());
      String uniqueHash = Strings.createSHA1Hash(stackTraceString);
      error.setGroupingHash(uniqueHash);
      return true;
    });

    MetaData metadata = new MetaData();
    metadata.addToTab("LeakInfo", "LeakInfo", leakInfo);
    bugsnagClient.notifyBlocking(result.leakTraceAsFakeException(), Severity.ERROR, metadata);
  }
}

Next, you need to specify the listener service class in LeakCanary:

public class DebugExampleApplication extends ExampleApplication {
  @Override protected void installLeakCanary() {
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .listenerServiceClass(LeakUploadService.class);
      .buildAndInstall();
  }
}

Don't forget to register the service in your debug AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <application android:name="com.example.DebugExampleApplication">
    <service android:name="com.example.LeakUploadService" />
  </application>
</manifest>

Upvotes: 2

Related Questions