Merge pull request #2676 from square/py/improve_heap_diff
Iterating on heap growth based on friction / feedback from trying to integrate it inside Square.
diff --git a/docs/shark.md b/docs/shark.md
index 6e6d871..d5e6c82 100644
--- a/docs/shark.md
+++ b/docs/shark.md
@@ -92,84 +92,118 @@
### Reading records in a hprof file
```groovy
+// build.gradle
dependencies {
implementation 'com.squareup.leakcanary:shark-hprof:$sharkVersion'
}
```
```kotlin
-// Prints all class and field names
-Hprof.open(heapDumpFile)
- .use { hprof ->
- hprof.reader.readHprofRecords(
- recordTypes = setOf(StringRecord::class),
- listener = OnHprofRecordListener { position, record ->
- println((record as StringRecord).string)
- })
- }
+import java.io.File
+import shark.Hprof
+import shark.HprofRecord.StringRecord
+import shark.OnHprofRecordListener
+
+fun main(args: Array<String>) {
+ val heapDumpFile = File(args[0])
+
+ // Prints all class and field names
+ Hprof.open(heapDumpFile).use { hprof ->
+ hprof.reader.readHprofRecords(
+ recordTypes = setOf(StringRecord::class),
+ listener =
+ OnHprofRecordListener { position, record -> println((record as StringRecord).string) },
+ )
+ }
+}
```
### Navigating the heap object graph
```groovy
+// build.gradle
dependencies {
implementation 'com.squareup.leakcanary:shark-graph:$sharkVersion'
}
```
```kotlin
-// Prints all thread names
-Hprof.open(heapDumpFile)
- .use { hprof ->
- val heapGraph = HprofHeapGraph.indexHprof(hprof)
- val threadClass = heapGraph.findClassByName("java.lang.Thread")!!
- val threadNames: Sequence<String> = threadClass.instances.map { instance ->
+import java.io.File
+import shark.Hprof
+import shark.HprofHeapGraph
+
+fun main(args: Array<String>) {
+ val heapDumpFile = File(args[0])
+
+ // Prints all thread names
+ Hprof.open(heapDumpFile).use { hprof ->
+ val heapGraph = HprofHeapGraph.indexHprof(hprof)
+ val threadClass = heapGraph.findClassByName("java.lang.Thread")!!
+ val threadNames: Sequence<String> =
+ threadClass.instances.map { instance ->
val nameField = instance["java.lang.Thread", "name"]!!
nameField.value.readAsJavaString()!!
}
- threadNames.forEach { println(it) }
- }
+ threadNames.forEach { println(it) }
+ }
+}
```
### Generating a heap analysis report
```groovy
+// build.gradle
dependencies {
implementation 'com.squareup.leakcanary:shark:$sharkVersion'
}
```
```kotlin
+import java.io.File
+import shark.FilteringLeakingObjectFinder
+import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
+import shark.HeapAnalyzer
+import shark.HeapObject
+import shark.HeapObject.HeapInstance
+import shark.Hprof
+import shark.HprofHeapGraph
+
// Marks any instance of com.example.ThingWithLifecycle with
// ThingWithLifecycle.destroyed=true as leaking
-val leakingObjectFilter = object : LeakingObjectFilter {
- override fun isLeakingObject(heapObject: HeapObject): Boolean {
- return if (heapObject instanceOf "com.example.ThingWithLifecycle") {
- val instance = heapObject as HeapInstance
- val destroyedField = instance["com.example.ThingWithLifecycle", "destroyed"]!!
- destroyedField.value.asBoolean!!
- } else false
+val leakingObjectFilter =
+ object : LeakingObjectFilter {
+ override fun isLeakingObject(heapObject: HeapObject): Boolean {
+ return if (
+ heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle"
+ ) {
+ val destroyedField = heapObject["com.example.ThingWithLifecycle", "destroyed"]!!
+ destroyedField.value.asBoolean!!
+ } else false
+ }
}
-}
val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter))
-val heapAnalysis = Hprof.open(heapDumpFile)
- .use { hprof ->
+fun main(args: Array<String>) {
+ val heapDumpFile = File(args[0])
+ val heapAnalysis =
+ Hprof.open(heapDumpFile).use { hprof ->
val heapGraph = HprofHeapGraph.indexHprof(hprof)
- val heapAnalyzer = HeapAnalyzer(AnalyzerProgressListener.NONE)
+ val heapAnalyzer = HeapAnalyzer({})
heapAnalyzer.analyze(
- heapDumpFile = heapDumpFile,
- graph = heapGraph,
- leakingObjectFinder = leakingObjectFinder,
+ heapDumpFile = heapDumpFile,
+ graph = heapGraph,
+ leakingObjectFinder = leakingObjectFinder,
)
}
-println(analysis)
+ println(heapAnalysis)
+}
```
### Generating an Android heap analysis report
```groovy
+// build.gradle
dependencies {
implementation 'com.squareup.leakcanary:shark-android:$sharkVersion'
}
@@ -177,11 +211,50 @@
```kotlin
-val heapAnalyzer = HeapAnalyzer(AnalyzerProgressListener.NONE)
-val analysis = heapAnalyzer.checkForLeaks(
- heapDumpFile = heapDumpFile,
- referenceMatchers = AndroidReferenceMatchers.appDefaults,
- objectInspectors = AndroidObjectInspectors.appDefaults
-)
-println(analysis)
+import java.io.File
+import shark.AndroidObjectInspectors
+import shark.AndroidReferenceMatchers
+import shark.FilteringLeakingObjectFinder
+import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
+import shark.HeapAnalyzer
+import shark.HeapObject
+import shark.HeapObject.HeapInstance
+import shark.Hprof
+import shark.HprofHeapGraph
+
+// Marks any instance of com.example.ThingWithLifecycle with
+// ThingWithLifecycle.destroyed=true as leaking
+val leakingObjectFilter =
+ object : LeakingObjectFilter {
+ override fun isLeakingObject(heapObject: HeapObject): Boolean {
+ return if (
+ heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle"
+ ) {
+ val instance = heapObject as HeapInstance
+ val destroyedField = instance["com.example.ThingWithLifecycle", "destroyed"]!!
+ destroyedField.value.asBoolean!!
+ } else false
+ }
+ }
+
+val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter))
+
+fun main(args: Array<String>) {
+ val heapDumpFile = File(args[0])
+
+ val heapAnalysis =
+ Hprof.open(heapDumpFile).use { hprof ->
+ val heapGraph = HprofHeapGraph.indexHprof(hprof)
+ val heapAnalyzer = HeapAnalyzer({})
+ heapAnalyzer.analyze(
+ heapDumpFile = heapDumpFile,
+ graph = heapGraph,
+ leakingObjectFinder = leakingObjectFinder,
+ referenceMatchers = AndroidReferenceMatchers.appDefaults,
+ objectInspectors = AndroidObjectInspectors.appDefaults,
+ )
+ }
+
+ println(heapAnalysis)
+}
```
diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/RequestPermissionActivity.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/RequestPermissionActivity.kt
index 74b47ee..fdff077 100644
--- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/RequestPermissionActivity.kt
+++ b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/RequestPermissionActivity.kt
@@ -56,7 +56,7 @@
grantResults: IntArray
) {
if (!hasTargetPermission()) {
- Toast.makeText(application, R.string.leak_canary_permission_not_granted, LENGTH_LONG)
+ Toast.makeText(this, R.string.leak_canary_permission_not_granted, LENGTH_LONG)
.show()
}
finish()
diff --git a/shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt b/shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
index 2b28418..96292f5 100644
--- a/shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
+++ b/shark/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
@@ -871,7 +871,7 @@
STUB {
override fun inspect(reporter: ObjectReporter) {
reporter.whenInstanceOf("android.os.Binder") { instance ->
- labels + "${instance.instanceClassSimpleName} is a binder stub. Binder stubs will often be" +
+ labels += "${instance.instanceClassSimpleName} is a binder stub. Binder stubs will often be" +
" retained long after the associated activity or service is destroyed, as by design stubs" +
" are retained until the other side gets GCed. If ${instance.instanceClassSimpleName} is" +
" not a *static* inner class then that's most likely the root cause of this leak. Make" +