Skip to content

Commit

Permalink
Add labels for receivers in LoadedAPK
Browse files Browse the repository at this point in the history
Fixes #2091
  • Loading branch information
pyricau committed Mar 26, 2021
1 parent 295f948 commit 233066b
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ package com.example.leakcanary

import android.app.Activity
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Handler
import android.os.StatFs
import android.os.SystemClock
import android.view.View
Expand All @@ -30,14 +34,39 @@ import kotlin.random.Random

class MainActivity : Activity() {

private var leakyReceiver = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)

val app = application as ExampleApplication
val leakedView = findViewById<View>(R.id.helper_text)

findViewById<Button>(R.id.recreate_activity_button).setOnClickListener { recreate() }
findViewById<Button>(R.id.leak_activity_button).setOnClickListener {
val leakedView = findViewById<View>(R.id.helper_text)
when (Random.nextInt(4)) {
// Leak from application class
0 -> app.leakedViews.add(leakedView)
// Leak from Kotlin object singleton
1 -> LeakingSingleton.leakedViews.add(leakedView)
2 -> {
// Leak from local variable on thread
val ref = AtomicReference(this)
val thread = Thread {
val activity = ref.get()
ref.set(null)
while (true) {
print(activity)
SystemClock.sleep(1000)
}
}
thread.name = "Leaking local variables"
thread.start()
}
// Leak from thread fields
else -> LeakingThread.thread.leakedViews.add(leakedView)
}
}
findViewById<Button>(R.id.show_dialog_button).setOnClickListener {
AlertDialog.Builder(this)
.setTitle("Leaky dialog")
Expand All @@ -49,28 +78,25 @@ class MainActivity : Activity() {
findViewById<Button>(R.id.start_service_button).setOnClickListener {
startService(Intent(this, LeakingService::class.java))
}
findViewById<Button>(R.id.leak_receiver_button).setOnClickListener {
leakyReceiver = true
recreate()
}
}

when (Random.nextInt(4)) {
// Leak from application class
0 -> app.leakedViews.add(leakedView)
// Leak from Kotlin object singleton
1 -> LeakingSingleton.leakedViews.add(leakedView)
2 -> {
// Leak from local variable on thread
val ref = AtomicReference(this)
val thread = Thread {
val activity = ref.get()
ref.set(null)
while (true) {
print(activity)
SystemClock.sleep(1000)
}
}
thread.name = "Leaking local variables"
thread.start()
}
// Leak from thread fields
else -> LeakingThread.thread.leakedViews.add(leakedView)
class NoOpBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) = Unit
}

override fun onDestroy() {
super.onDestroy()
if (leakyReceiver) {
Handler().postDelayed({
registerReceiver(NoOpBroadcastReceiver(), IntentFilter())
}, 500)
}
}
}
17 changes: 16 additions & 1 deletion leakcanary-android-sample/src/main/res/layout/main_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:text="@string/recreate_activity_button_text"
android:text="Recreate Activity"
/>
<Button
android:id="@+id/leak_activity_button"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:text="Leak Activity"
/>
<Button
android:id="@+id/show_dialog_button"
Expand All @@ -50,5 +57,13 @@
android:layout_height="wrap_content"
android:text="@string/start_service_button_text"
/>
<Button
android:id="@+id/leak_receiver_button"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:text="Create &amp; leak Receiver"
/>


</LinearLayout>
1 change: 0 additions & 1 deletion leakcanary-android-sample/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@
tools:ignore="MissingTranslation">
<string name="app_name">Example App</string>
<string name="helper_text">Press this button or rotate the screen to create leaks. Pressing back or creating 5 leaks will trigger a heap dump.</string>
<string name="recreate_activity_button_text">Recreate &amp; Leak Activity</string>
<string name="start_service_button_text">Create &amp; Leak Service</string>
</resources>
1 change: 1 addition & 0 deletions shark-android/api/shark-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public abstract class shark/AndroidObjectInspectors : java/lang/Enum, shark/Obje
public static final field EDITOR Lshark/AndroidObjectInspectors;
public static final field FRAGMENT Lshark/AndroidObjectInspectors;
public static final field INPUT_METHOD_MANAGER Lshark/AndroidObjectInspectors;
public static final field LOADED_APK Lshark/AndroidObjectInspectors;
public static final field MAIN_THREAD Lshark/AndroidObjectInspectors;
public static final field MESSAGE Lshark/AndroidObjectInspectors;
public static final field MESSAGE_QUEUE Lshark/AndroidObjectInspectors;
Expand Down
42 changes: 42 additions & 0 deletions shark-android/src/main/java/shark/AndroidObjectInspectors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,48 @@ enum class AndroidObjectInspectors : ObjectInspector {
}
},

LOADED_APK {
override fun inspect(
reporter: ObjectReporter
) {
reporter.whenInstanceOf("android.app.LoadedApk") { instance ->
val receiversMap = instance["android.app.LoadedApk", "mReceivers"]!!.valueAsInstance!!
val receiversArray = receiversMap["android.util.ArrayMap", "mArray"]!!.valueAsObjectArray!!
val receiverContextList = receiversArray.readElements().toList()

val allReceivers = (receiverContextList.indices step 2).map { index ->
val context = receiverContextList[index]
if (context.isNonNullReference) {
val contextReceiversMap = receiverContextList[index + 1].asObject!!.asInstance!!
val contextReceivers = contextReceiversMap["android.util.ArrayMap", "mArray"]!!
.valueAsObjectArray!!
.readElements()
.toList()

val receivers =
(contextReceivers.indices step 2).mapNotNull { contextReceivers[it].asObject?.asInstance }
val contextInstance = context.asObject!!.asInstance!!
val contextString =
"${contextInstance.instanceClassSimpleName}@${contextInstance.objectId}"
contextString to receivers.map { "${it.instanceClassSimpleName}@${it.objectId}" }
} else {
null
}
}.filterNotNull().toList()

if (allReceivers.isNotEmpty()) {
labels += "Receivers"
allReceivers.forEach { (contextString, receiverStrings) ->
labels += "..$contextString"
receiverStrings.forEach { receiverString ->
labels += "....$receiverString"
}
}
}
}
}
},

MORTAR_PRESENTER {
override fun inspect(
reporter: ObjectReporter
Expand Down

0 comments on commit 233066b

Please sign in to comment.