Skip to content

Commit

Permalink
API for customizing heap dumping
Browse files Browse the repository at this point in the history
Fixes #2214
  • Loading branch information
pyricau committed Dec 22, 2021
1 parent 14b2e21 commit 4147c89
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 200 deletions.
53 changes: 48 additions & 5 deletions leakcanary-android-core/api/leakcanary-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ public final class com/squareup/leakcanary/core/BuildConfig {
public fun <init> ()V
}

public final class leakcanary/AndroidDebugHeapDumper : leakcanary/HeapDumper {
public static final field INSTANCE Lleakcanary/AndroidDebugHeapDumper;
public fun dumpHeap (Lleakcanary/HeapDumper$DumpLocation;)Lleakcanary/HeapDumper$Result;
}

public final class leakcanary/DefaultOnHeapAnalyzedListener : leakcanary/OnHeapAnalyzedListener {
public static final field Companion Lleakcanary/DefaultOnHeapAnalyzedListener$Companion;
public fun <init> (Landroid/app/Application;)V
Expand All @@ -18,6 +23,41 @@ public final class leakcanary/DefaultOnHeapAnalyzedListener$Companion {
public final fun create ()Lleakcanary/OnHeapAnalyzedListener;
}

public abstract interface class leakcanary/HeapDumper {
public abstract fun dumpHeap (Lleakcanary/HeapDumper$DumpLocation;)Lleakcanary/HeapDumper$Result;
}

public abstract class leakcanary/HeapDumper$DumpLocation {
}

public final class leakcanary/HeapDumper$DumpLocation$FileLocation : leakcanary/HeapDumper$DumpLocation {
public fun <init> (Ljava/io/File;)V
public final fun getFile ()Ljava/io/File;
}

public final class leakcanary/HeapDumper$DumpLocation$Unspecified : leakcanary/HeapDumper$DumpLocation {
public static final field INSTANCE Lleakcanary/HeapDumper$DumpLocation$Unspecified;
}

public abstract class leakcanary/HeapDumper$Result {
}

public final class leakcanary/HeapDumper$Result$Failure : leakcanary/HeapDumper$Result {
public fun <init> (Ljava/lang/Throwable;)V
public final fun getException ()Ljava/lang/Throwable;
}

public final class leakcanary/HeapDumper$Result$HeapDump : leakcanary/HeapDumper$Result {
public fun <init> (Ljava/io/File;J)V
public final fun getDurationMillis ()J
public final fun getFile ()Ljava/io/File;
}

public final class leakcanary/HeapDumpersKt {
public static final fun leakCanaryHeapDumper (Lleakcanary/HeapDumper;ZZ)Lleakcanary/HeapDumper;
public static synthetic fun leakCanaryHeapDumper$default (Lleakcanary/HeapDumper;ZZILjava/lang/Object;)Lleakcanary/HeapDumper;
}

public final class leakcanary/LeakCanary {
public static final field INSTANCE Lleakcanary/LeakCanary;
public final fun dumpHeap ()V
Expand All @@ -29,12 +69,13 @@ public final class leakcanary/LeakCanary {

public final class leakcanary/LeakCanary$Config {
public fun <init> ()V
public fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Z)V
public synthetic fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Z)V
public synthetic fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component10 ()Z
public final fun component11 ()Lshark/LeakingObjectFinder;
public final fun component12 ()Z
public final fun component12 ()Lleakcanary/HeapDumper;
public final fun component13 ()Z
public final fun component2 ()Z
public final fun component3 ()I
public final fun component4 ()Ljava/util/List;
Expand All @@ -43,12 +84,13 @@ public final class leakcanary/LeakCanary$Config {
public final fun component7 ()Lshark/MetadataExtractor;
public final fun component8 ()Z
public final fun component9 ()I
public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Z)Lleakcanary/LeakCanary$Config;
public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config;
public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Z)Lleakcanary/LeakCanary$Config;
public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config;
public fun equals (Ljava/lang/Object;)Z
public final fun getComputeRetainedHeapSize ()Z
public final fun getDumpHeap ()Z
public final fun getDumpHeapWhenDebugging ()Z
public final fun getHeapDumper ()Lleakcanary/HeapDumper;
public final fun getLeakingObjectFinder ()Lshark/LeakingObjectFinder;
public final fun getMaxStoredHeapDumps ()I
public final fun getMetadataExtractor ()Lshark/MetadataExtractor;
Expand All @@ -68,6 +110,7 @@ public final class leakcanary/LeakCanary$Config$Builder {
public final fun computeRetainedHeapSize (Z)Lleakcanary/LeakCanary$Config$Builder;
public final fun dumpHeap (Z)Lleakcanary/LeakCanary$Config$Builder;
public final fun dumpHeapWhenDebugging (Z)Lleakcanary/LeakCanary$Config$Builder;
public final fun heapDumper (Lleakcanary/HeapDumper;)Lleakcanary/LeakCanary$Config$Builder;
public final fun leakingObjectFinder (Lshark/LeakingObjectFinder;)Lleakcanary/LeakCanary$Config$Builder;
public final fun maxStoredHeapDumps (I)Lleakcanary/LeakCanary$Config$Builder;
public final fun metadataExtractor (Lshark/MetadataExtractor;)Lleakcanary/LeakCanary$Config$Builder;
Expand Down
2 changes: 1 addition & 1 deletion leakcanary-android-core/consumer-proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Loaded via reflection & referenced by shark.AndroidReferenceMatchers.LEAK_CANARY_INTERNAL
-keep class leakcanary.internal.InternalLeakCanary { *; }
# Referenced by shark.AndroidReferenceMatchers.LEAK_CANARY_HEAP_DUMPER
-keep class leakcanary.internal.AndroidHeapDumper { *; }
-keep class leakcanary.internal.HeapDumperController { *; }
# Marshmallow removed Notification.setLatestEventInfo()
-dontwarn android.app.Notification
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package leakcanary

import android.os.Debug
import leakcanary.HeapDumper.DumpLocation
import leakcanary.HeapDumper.DumpLocation.FileLocation
import leakcanary.HeapDumper.Result
import leakcanary.HeapDumper.Result.Failure
import leakcanary.HeapDumper.Result.HeapDump
import leakcanary.internal.friendly.measureDurationMillis

/**
* Dumps the heap using [Debug.dumpHprofData]. [dumpHeap] is expected to be passed in a
* [FileLocation] and will otherwise fail.
*
* Note: measures the duration of the call to [Debug.dumpHprofData] using
* [android.os.SystemClock.uptimeMillis].
*/
object AndroidDebugHeapDumper : HeapDumper {
override fun dumpHeap(dumpLocation: DumpLocation): Result {
return if (dumpLocation is FileLocation) {
val outputFile = dumpLocation.file
try {
val durationMillis = measureDurationMillis {
Debug.dumpHprofData(outputFile.absolutePath)
}
if (outputFile.length() == 0L) {
Failure(RuntimeException("Dumped heap file is 0 byte length"))
} else {
HeapDump(outputFile, durationMillis)
}
} catch (e: Throwable) {
Failure(e)
}
} else {
Failure(RuntimeException("HeapDumper.DumpLocation is Unspecified"))
}
}
}
33 changes: 33 additions & 0 deletions leakcanary-android-core/src/main/java/leakcanary/HeapDumper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package leakcanary

import java.io.File

fun interface HeapDumper {

sealed class DumpLocation {
object Unspecified : DumpLocation()
class FileLocation(val file: File) : DumpLocation()
}

sealed class Result {
class Failure(val exception: Throwable) : Result()
class HeapDump(
val file: File,
val durationMillis: Long
) : Result()
}

/**
* Dumps the heap. The implementation is expected to be blocking until the heap is dumped
* or heap dumping failed.
*
* The [HeapDumper] interface is designed for delegation.
*
* [dumpLocation] may be [DumpLocation.Unspecified] or [DumpLocation.FileLocation], and
* it's ok for implementations to know how to deal with one or the other (or both) and otherwise
* return a [Result.Failure].
*
* @return [Result.HeapDump] if dumping the heap succeeded, and [Result.Failure] otherwise.
*/
fun dumpHeap(dumpLocation: DumpLocation): Result
}
32 changes: 32 additions & 0 deletions leakcanary-android-core/src/main/java/leakcanary/HeapDumpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package leakcanary

import leakcanary.internal.withManagedHeapDumpDirectory
import leakcanary.internal.withNotification
import leakcanary.internal.withResourceIdNames
import leakcanary.internal.withToast

fun leakCanaryHeapDumper(
/**
* The core [HeapDumper] which will be wrapped by delegates.
*/
coreHeapDumper: HeapDumper = AndroidDebugHeapDumper,
/**
* Wraps [HeapDumper.dumpHeap] to show cute canary Toast while the heap is being dumped.
*/
toastOnDump: Boolean = true,
/**
* Wraps [HeapDumper.dumpHeap] to show an Android notification that says "Dumping Heap" while the
* heap is being dumped.
*/
notificationOnDump: Boolean = true
): HeapDumper {
var heapDumper = coreHeapDumper.withResourceIdNames()
if (notificationOnDump) {
heapDumper = heapDumper.withNotification()
}
if (toastOnDump) {
heapDumper = heapDumper.withToast()
}
heapDumper = heapDumper.withManagedHeapDumpDirectory()
return heapDumper
}
23 changes: 22 additions & 1 deletion leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,21 @@ object LeakCanary {
*/
val leakingObjectFinder: LeakingObjectFinder = KeyedWeakReferenceFinder,

/**
* Dumps the Java heap. You may replace this with your own implementation if you wish to
* change how LeakCanary notifies the user of that the heap is dumping, or the core heap
* dumping implementation. It's highly recommended that you call [leakCanaryHeapDumper] and
* either provide a custom core heap dumper or wrap the returned heap dumper with a delegate
* that perform additional work. Example usage:
*
* ```
* LeakCanary.config = LeakCanary.config.copy(
* heapDumper = leakCanaryHeapDumper(toastOnDump = false)
* )
* ```
*/
val heapDumper: HeapDumper = leakCanaryHeapDumper(),

/**
* Deprecated: This is a no-op, set a custom [leakingObjectFinder] instead.
*/
Expand Down Expand Up @@ -213,6 +228,7 @@ object LeakCanary {
private var requestWriteExternalStoragePermission =
config.requestWriteExternalStoragePermission
private var leakingObjectFinder = config.leakingObjectFinder
private var heapDumper = config.heapDumper

/** @see [LeakCanary.Config.dumpHeap] */
fun dumpHeap(dumpHeap: Boolean) =
Expand Down Expand Up @@ -258,6 +274,10 @@ object LeakCanary {
fun leakingObjectFinder(leakingObjectFinder: LeakingObjectFinder) =
apply { this.leakingObjectFinder = leakingObjectFinder }

/** @see [LeakCanary.Config.heapDumper] */
fun heapDumper(heapDumper: HeapDumper) =
apply { this.heapDumper = heapDumper }

fun build() = config.copy(
dumpHeap = dumpHeap,
dumpHeapWhenDebugging = dumpHeapWhenDebugging,
Expand All @@ -269,7 +289,8 @@ object LeakCanary {
computeRetainedHeapSize = computeRetainedHeapSize,
maxStoredHeapDumps = maxStoredHeapDumps,
requestWriteExternalStoragePermission = requestWriteExternalStoragePermission,
leakingObjectFinder = leakingObjectFinder
leakingObjectFinder = leakingObjectFinder,
heapDumper = heapDumper
)
}
}
Expand Down

This file was deleted.

Loading

0 comments on commit 4147c89

Please sign in to comment.