diff --git a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt index f5fc7eb3a3..9189571713 100644 --- a/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt +++ b/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt @@ -1,6 +1,7 @@ package leakcanary import android.content.Intent +import java.util.ServiceLoader import leakcanary.LeakCanary.config import leakcanary.internal.HeapDumpControl import leakcanary.internal.InternalLeakCanary @@ -88,9 +89,13 @@ object LeakCanary { * heap. You can create your own [ObjectInspector] implementations, and also add * a [shark.AppSingletonInspector] instance created with the list of internal singletons. * - * Defaults to [AndroidObjectInspectors.appDefaults] + * Defaults to [AndroidObjectInspectors.appDefaults] and any discoverable [ObjectInspector] + * through the classpath at `META-INF/services/shark.ObjectInspector`. + * + * @see [ServiceLoader] */ - val objectInspectors: List = AndroidObjectInspectors.appDefaults, + val objectInspectors: List = + AndroidObjectInspectors.appDefaults + ServiceLoader.load(ObjectInspector::class.java), /** * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata]. @@ -182,6 +187,11 @@ object LeakCanary { * ) * } * ``` + * + * Any [EventListener] discoverable through the classpath at + * `META-INF/services/leakcanary.EventListener` will be automatically loaded. + * + * @see [ServiceLoader] */ val eventListeners: List = listOf( LogcatEventListener, @@ -194,8 +204,8 @@ object LeakCanary { RemoteWorkManagerHeapAnalyzer WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer else -> BackgroundThreadHeapAnalyzer - } - ), + }, + ) + ServiceLoader.load(EventListener::class.java), /** * Whether to show LeakCanary notifications. When [showNotifications] is true, LeakCanary diff --git a/leakcanary/leakcanary-android-core/src/test/java/leakcanary/LeakCanaryConfigTest.kt b/leakcanary/leakcanary-android-core/src/test/java/leakcanary/LeakCanaryConfigTest.kt index 9e3ffbd1d9..c583158706 100644 --- a/leakcanary/leakcanary-android-core/src/test/java/leakcanary/LeakCanaryConfigTest.kt +++ b/leakcanary/leakcanary-android-core/src/test/java/leakcanary/LeakCanaryConfigTest.kt @@ -4,6 +4,8 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.memberProperties +import shark.ObjectInspector +import shark.ObjectReporter class LeakCanaryConfigTest { @@ -22,4 +24,22 @@ class LeakCanaryConfigTest { private fun configProperties() = LeakCanary.Config::class.memberProperties .map { it.name } + + @Test fun `LeakCanary Config loads extensions from classpath`() { + val config = LeakCanary.Config() + + assertThat(config.objectInspectors.filterIsInstance()).size().isEqualTo(1) + assertThat(config.eventListeners.filterIsInstance()).size().isEqualTo(1) + } + + class TestObjectInspector : ObjectInspector { + override fun inspect(reporter: ObjectReporter) { + } + } + + class TestEventListener : EventListener { + override fun onEvent(event: EventListener.Event) { + } + } + } diff --git a/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/leakcanary.EventListener b/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/leakcanary.EventListener new file mode 100644 index 0000000000..b99a1d8aa2 --- /dev/null +++ b/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/leakcanary.EventListener @@ -0,0 +1 @@ +leakcanary.LeakCanaryConfigTest$TestEventListener diff --git a/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/shark.ObjectInspector b/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/shark.ObjectInspector new file mode 100644 index 0000000000..d8252d61d3 --- /dev/null +++ b/leakcanary/leakcanary-android-core/src/test/resources/META-INF/services/shark.ObjectInspector @@ -0,0 +1 @@ +leakcanary.LeakCanaryConfigTest$TestObjectInspector diff --git a/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksAssert.kt b/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksAssert.kt index 6873ef9fdf..568802d411 100644 --- a/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksAssert.kt +++ b/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksAssert.kt @@ -1,10 +1,13 @@ package leakcanary +import java.util.ServiceLoader + /** * The interface for the implementation that [LeakAssertions.assertNoLeaks] delegates to. * You can call [DetectLeaksAssert.update] to provide your own implementation. * - * The default implementation is [AndroidDetectLeaksAssert]. + * The default implementation is [AndroidDetectLeaksAssert] or to a discoverable [DetectLeaksAssert] + * through the classpath at `META-INF/services/leakcanary.DetectLeaksAssert`. */ fun interface DetectLeaksAssert { @@ -12,7 +15,9 @@ fun interface DetectLeaksAssert { companion object { @Volatile - internal var delegate: DetectLeaksAssert = AndroidDetectLeaksAssert() + internal var delegate: DetectLeaksAssert = + ServiceLoader.load(DetectLeaksAssert::class.java).singleOrNull() + ?: AndroidDetectLeaksAssert() fun update(delegate: DetectLeaksAssert) { DetectLeaksAssert.delegate = delegate