Add plugin for LeakCanary 2 (#1959)
Summary: Add plugin for LeakCanary 2 as requested various times: https://github.com/facebook/flipper/issues/1379 https://github.com/facebook/flipper/issues/832 https://github.com/square/leakcanary/issues/1777 ## Changelog * Adds a leakcanary2 plugin for Android * Adds support for leakcanary2 to existing desktop plugin Pull Request resolved: https://github.com/facebook/flipper/pull/1959 Test Plan: * Docs updated to show new implementation * Should old leakcanary plugin in sample be replaced? Reviewed By: mweststrate Differential Revision: D26691637 Pulled By: passy fbshipit-source-id: 5e236fa6cc124f0720a6b21b5ee7c117ccf96fbf
This commit is contained in:
committed by
Facebook GitHub Bot
parent
61eabf372a
commit
4d8be35d1a
31
android/plugins/leakcanary2/build.gradle
Normal file
31
android/plugins/leakcanary2/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'maven'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion rootProject.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.targetSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||||
|
implementation project(':android')
|
||||||
|
implementation deps.leakcanary2
|
||||||
|
compileOnly deps.jsr305
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.vanniktech.maven.publish'
|
||||||
8
android/plugins/leakcanary2/gradle.properties
Normal file
8
android/plugins/leakcanary2/gradle.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# This source code is licensed under the MIT license found in the LICENSE
|
||||||
|
# file in the root directory of this source tree.
|
||||||
|
|
||||||
|
POM_NAME=Flipper LeakCanary2 Plugin
|
||||||
|
POM_DESCRIPTION=LeakCanary2 plugin for Flipper
|
||||||
|
POM_ARTIFACT_ID=flipper-leakcanary2-plugin
|
||||||
|
POM_PACKAGING=aar
|
||||||
|
|
||||||
11
android/plugins/leakcanary2/src/main/AndroidManifest.xml
Normal file
11
android/plugins/leakcanary2/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
~
|
||||||
|
~ This source code is licensed under the MIT license found in the LICENSE
|
||||||
|
~ file in the root directory of this source tree.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.facebook.flipper.plugins.leakcanary2">
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.leakcanary2
|
||||||
|
|
||||||
|
import com.facebook.flipper.android.AndroidFlipperClient
|
||||||
|
import leakcanary.DefaultOnHeapAnalyzedListener
|
||||||
|
import leakcanary.OnHeapAnalyzedListener
|
||||||
|
import shark.HeapAnalysis
|
||||||
|
import shark.HeapAnalysisSuccess
|
||||||
|
|
||||||
|
class FlipperLeakListener : OnHeapAnalyzedListener {
|
||||||
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
|
|
||||||
|
private val defaultListener = DefaultOnHeapAnalyzedListener.create()
|
||||||
|
|
||||||
|
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
|
||||||
|
leaks.addAll(heapAnalysis.toLeakList())
|
||||||
|
|
||||||
|
AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
||||||
|
(client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)
|
||||||
|
?.reportLeaks(leaks)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultListener.onHeapAnalyzed(heapAnalysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HeapAnalysis.toLeakList(): List<Leak> {
|
||||||
|
return if (this is HeapAnalysisSuccess) {
|
||||||
|
allLeaks.mapNotNull {
|
||||||
|
if (it.leakTraces.isNotEmpty()) {
|
||||||
|
it.leakTraces[0].toLeak(it.shortDescription)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.leakcanary2
|
||||||
|
|
||||||
|
import com.facebook.flipper.core.FlipperConnection
|
||||||
|
import com.facebook.flipper.core.FlipperPlugin
|
||||||
|
|
||||||
|
private const val REPORT_LEAK_EVENT = "reportLeak2"
|
||||||
|
private const val CLEAR_EVENT = "clear"
|
||||||
|
|
||||||
|
class LeakCanary2FlipperPlugin : FlipperPlugin {
|
||||||
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
|
private val alreadySeenLeakSignatures: MutableSet<String> = mutableSetOf()
|
||||||
|
private var connection: FlipperConnection? = null
|
||||||
|
|
||||||
|
override fun getId() = ID
|
||||||
|
|
||||||
|
override fun onConnect(connection: FlipperConnection?) {
|
||||||
|
this.connection = connection
|
||||||
|
connection?.receive(CLEAR_EVENT) { _, _ -> leaks.clear() }
|
||||||
|
sendLeakList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisconnect() {
|
||||||
|
connection = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runInBackground() = false
|
||||||
|
|
||||||
|
internal fun reportLeaks(leaks: List<Leak>) {
|
||||||
|
for (leak in leaks) {
|
||||||
|
if (leak.signature !in alreadySeenLeakSignatures) {
|
||||||
|
this.leaks.add(leak)
|
||||||
|
alreadySeenLeakSignatures.add(leak.signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLeakList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendLeakList() {
|
||||||
|
connection?.send(REPORT_LEAK_EVENT, LeakCanary2Report(leaks).toFlipperObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ID = "LeakCanary"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.leakcanary2
|
||||||
|
|
||||||
|
import com.facebook.flipper.core.FlipperArray
|
||||||
|
import com.facebook.flipper.core.FlipperObject
|
||||||
|
import com.facebook.flipper.core.FlipperValue
|
||||||
|
import shark.LeakTrace
|
||||||
|
import shark.LeakTraceObject
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
internal data class LeakCanary2Report(val leaks: List<Leak>) : FlipperValue {
|
||||||
|
override fun toFlipperObject(): FlipperObject = FlipperObject.Builder()
|
||||||
|
.put("leaks", leaks.map { it.toFlipperObject() }.toFlipperArray())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class Leak(
|
||||||
|
val title: String,
|
||||||
|
val root: String,
|
||||||
|
val elements: Map<String, Element>,
|
||||||
|
val retainedSize: String,
|
||||||
|
val signature: String,
|
||||||
|
val details: String
|
||||||
|
) : FlipperValue {
|
||||||
|
override fun toFlipperObject(): FlipperObject {
|
||||||
|
return FlipperObject.Builder()
|
||||||
|
.put("title", title)
|
||||||
|
.put("root", root)
|
||||||
|
.put("elements", elements.toFlipperObject())
|
||||||
|
.put("retainedSize", retainedSize)
|
||||||
|
.put("details", details)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Map<String, FlipperValue>.toFlipperObject(): FlipperObject =
|
||||||
|
mapValues { it.value.toFlipperObject() }.toFlipperObject()
|
||||||
|
|
||||||
|
@JvmName("toFlipperObjectStringFlipperObject")
|
||||||
|
private fun Map<String, FlipperObject>.toFlipperObject(): FlipperObject =
|
||||||
|
asIterable()
|
||||||
|
.fold(FlipperObject.Builder()) { builder, entry ->
|
||||||
|
builder.put(entry.key, entry.value)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun LeakTrace.toLeak(title: String): Leak {
|
||||||
|
val elements = getElements()
|
||||||
|
return Leak(
|
||||||
|
title = title,
|
||||||
|
elements = elements.toMap(),
|
||||||
|
retainedSize = retainedHeapByteSize?.let { "$it bytes" } ?: "unknown size",
|
||||||
|
signature = signature,
|
||||||
|
root = elements.first().first,
|
||||||
|
details = "$this"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LeakTrace.getElements(): List<Pair<String, Element>> {
|
||||||
|
val referenceElements = referencePath.map { reference ->
|
||||||
|
val id = UUID.randomUUID().toString()
|
||||||
|
id to Element(id, reference.originObject)
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
|
val leakId = UUID.randomUUID().toString()
|
||||||
|
referenceElements.add(leakId to Element(leakId, leakingObject))
|
||||||
|
|
||||||
|
return referenceElements.mapIndexed { index, pair ->
|
||||||
|
pair.first to if (index == referenceElements.lastIndex) pair.second else pair.second.copy(
|
||||||
|
children = listOf(referenceElements[index + 1].second.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class Element(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val expanded: Boolean = true,
|
||||||
|
val children: List<String> = emptyList(),
|
||||||
|
val attributes: List<ElementAttribute>,
|
||||||
|
val decoration: String = ""
|
||||||
|
) : FlipperValue {
|
||||||
|
constructor(id: String, leakObject: LeakTraceObject) : this(
|
||||||
|
id = id,
|
||||||
|
name = "${leakObject.className} (${leakObject.typeName})",
|
||||||
|
attributes = listOf(
|
||||||
|
ElementAttribute("leaking", leakObject.leakingStatus.shortName),
|
||||||
|
ElementAttribute("retaining", leakObject.retaining)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toFlipperObject(): FlipperObject {
|
||||||
|
return FlipperObject.Builder()
|
||||||
|
.put("id", id)
|
||||||
|
.put("name", name)
|
||||||
|
.put("expanded", expanded)
|
||||||
|
.put("children", children.toFlipperArray())
|
||||||
|
.put("attributes", attributes.toFlipperArray())
|
||||||
|
.put("data", EMPTY_FLIPPER_OBJECT)
|
||||||
|
.put("decoration", decoration)
|
||||||
|
.put("extraInfo", EMPTY_FLIPPER_OBJECT)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("toFlipperArrayFlipperValue")
|
||||||
|
private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
|
||||||
|
map { it.toFlipperObject() }.toFlipperArray()
|
||||||
|
|
||||||
|
@JvmName("toFlipperArrayString")
|
||||||
|
private fun Iterable<String>.toFlipperArray(): FlipperArray =
|
||||||
|
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Iterable<FlipperObject>.toFlipperArray(): FlipperArray =
|
||||||
|
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
||||||
|
|
||||||
|
private val LeakTraceObject.LeakingStatus.shortName: String
|
||||||
|
get() = when (this) {
|
||||||
|
LeakTraceObject.LeakingStatus.NOT_LEAKING -> "N"
|
||||||
|
LeakTraceObject.LeakingStatus.LEAKING -> "Y"
|
||||||
|
LeakTraceObject.LeakingStatus.UNKNOWN -> "?"
|
||||||
|
}
|
||||||
|
private val LeakTraceObject.retaining: String
|
||||||
|
get() = retainedHeapByteSize?.let { "$it bytes ($retainedObjectCount objects)" } ?: "unknown"
|
||||||
|
|
||||||
|
private val EMPTY_FLIPPER_OBJECT = FlipperObject.Builder().build()
|
||||||
|
|
||||||
|
data class ElementAttribute(
|
||||||
|
val name: String,
|
||||||
|
val value: String
|
||||||
|
) : FlipperValue {
|
||||||
|
override fun toFlipperObject(): FlipperObject {
|
||||||
|
return FlipperObject.Builder()
|
||||||
|
.put("name", name)
|
||||||
|
.put("value", value)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ buildscript {
|
|||||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||||
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2'
|
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
|
||||||
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +102,7 @@ ext.deps = [
|
|||||||
mockito : 'org.mockito:mockito-core:2.26.0',
|
mockito : 'org.mockito:mockito-core:2.26.0',
|
||||||
okhttp3 : 'com.squareup.okhttp3:okhttp:3.14.1',
|
okhttp3 : 'com.squareup.okhttp3:okhttp:3.14.1',
|
||||||
leakcanary : 'com.squareup.leakcanary:leakcanary-android:1.6.3',
|
leakcanary : 'com.squareup.leakcanary:leakcanary-android:1.6.3',
|
||||||
|
leakcanary2 : 'com.squareup.leakcanary:leakcanary-android:2.6',
|
||||||
testCore : 'androidx.test:core:1.1.0',
|
testCore : 'androidx.test:core:1.1.0',
|
||||||
testRules : 'androidx.test:rules:1.1.0',
|
testRules : 'androidx.test:rules:1.1.0',
|
||||||
// Plugin dependencies
|
// Plugin dependencies
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ type LeakReport = {
|
|||||||
leaks: string[];
|
leaks: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type LeakCanary2Report = {
|
||||||
|
leaks: Leak2[];
|
||||||
|
};
|
||||||
|
|
||||||
export type Fields = {[key: string]: string};
|
export type Fields = {[key: string]: string};
|
||||||
export type Leak = {
|
export type Leak = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -45,6 +49,15 @@ export type Leak = {
|
|||||||
instanceFields: {[key: string]: Fields};
|
instanceFields: {[key: string]: Fields};
|
||||||
staticFields: {[key: string]: Fields};
|
staticFields: {[key: string]: Fields};
|
||||||
retainedSize: string;
|
retainedSize: string;
|
||||||
|
details?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Leak2 = {
|
||||||
|
title: string;
|
||||||
|
root: string;
|
||||||
|
elements: {[key: string]: Element};
|
||||||
|
retainedSize: string;
|
||||||
|
details: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Window = styled(FlexRow)({
|
const Window = styled(FlexRow)({
|
||||||
@@ -72,23 +85,43 @@ export default class LeakCanary<PersistedState> extends FlipperPlugin<
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.client.subscribe('reportLeak', (results: LeakReport) => {
|
this.client.subscribe('reportLeak', (results: LeakReport) => {
|
||||||
// We only process new leaks instead of replacing the whole list in order
|
this._addNewLeaks(processLeaks(results.leaks));
|
||||||
// to both avoid redundant processing and to preserve the expanded/
|
});
|
||||||
// collapsed state of the tree view
|
|
||||||
const newLeaks = processLeaks(results.leaks.slice(this.state.leaksCount));
|
|
||||||
|
|
||||||
const leaks = this.state.leaks;
|
this.client.subscribe('reportLeak2', (results: LeakCanary2Report) => {
|
||||||
for (let i = 0; i < newLeaks.length; i++) {
|
this._addNewLeaks(results.leaks.map(this._adaptLeak2));
|
||||||
leaks.push(newLeaks[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
leaks: leaks,
|
|
||||||
leaksCount: results.leaks.length,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_addNewLeaks = (incomingLeaks: Leak[]) => {
|
||||||
|
// We only process new leaks instead of replacing the whole list in order
|
||||||
|
// to both avoid redundant processing and to preserve the expanded/
|
||||||
|
// collapsed state of the tree view
|
||||||
|
const newLeaks = incomingLeaks.slice(this.state.leaksCount);
|
||||||
|
const leaks = this.state.leaks;
|
||||||
|
for (let i = 0; i < newLeaks.length; i++) {
|
||||||
|
leaks.push(newLeaks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
leaks: leaks,
|
||||||
|
leaksCount: leaks.length,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_adaptLeak2 = (leak: Leak2): Leak => {
|
||||||
|
return {
|
||||||
|
title: leak.title,
|
||||||
|
root: leak.root,
|
||||||
|
elements: leak.elements,
|
||||||
|
elementsSimple: leak.elements,
|
||||||
|
staticFields: {},
|
||||||
|
instanceFields: {},
|
||||||
|
retainedSize: leak.retainedSize,
|
||||||
|
details: leak.details,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
_clearLeaks = () => {
|
_clearLeaks = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
leaks: [],
|
leaks: [],
|
||||||
@@ -154,20 +187,29 @@ export default class LeakCanary<PersistedState> extends FlipperPlugin<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar position="right" width={600} minWidth={300} maxWidth={900}>
|
<Sidebar position="right" width={600} minWidth={300} maxWidth={900}>
|
||||||
<Panel heading={'Instance'} floating={false} grow={false}>
|
{instanceFields && (
|
||||||
<ManagedDataInspector
|
<Panel heading={'Instance'} floating={false} grow={false}>
|
||||||
data={instanceFields}
|
<ManagedDataInspector
|
||||||
expandRoot={true}
|
data={instanceFields}
|
||||||
extractValue={this._extractValue}
|
expandRoot={true}
|
||||||
/>
|
extractValue={this._extractValue}
|
||||||
</Panel>
|
/>
|
||||||
<Panel heading={'Static'} floating={false} grow={false}>
|
</Panel>
|
||||||
<ManagedDataInspector
|
)}
|
||||||
data={staticFields}
|
{staticFields && (
|
||||||
expandRoot={true}
|
<Panel heading={'Static'} floating={false} grow={false}>
|
||||||
extractValue={this._extractValue}
|
<ManagedDataInspector
|
||||||
/>
|
data={staticFields}
|
||||||
</Panel>
|
expandRoot={true}
|
||||||
|
extractValue={this._extractValue}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
|
{leak.details && (
|
||||||
|
<Panel heading={'Details'} floating={false} grow={false}>
|
||||||
|
<pre>{leak.details}</pre>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -184,6 +226,7 @@ export default class LeakCanary<PersistedState> extends FlipperPlugin<
|
|||||||
const elements = showFullClassPaths
|
const elements = showFullClassPaths
|
||||||
? leak.elements
|
? leak.elements
|
||||||
: leak.elementsSimple;
|
: leak.elementsSimple;
|
||||||
|
|
||||||
const selected = selectedIdx == idx ? selectedEid : null;
|
const selected = selectedIdx == idx ? selectedEid : null;
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
|
|||||||
49
docs/setup/leak-canary-2-plugin.mdx
Normal file
49
docs/setup/leak-canary-2-plugin.mdx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
id: leak-canary-plugin
|
||||||
|
title: LeakCanary Setup
|
||||||
|
sidebar_label: LeakCanary
|
||||||
|
---
|
||||||
|
|
||||||
|
Ensure that you already have an explicit dependency in your application's
|
||||||
|
`build.gradle` including the plugin dependency, e.g.
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.76.0'
|
||||||
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update your the `onCreate` method in you `Application` to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary
|
||||||
|
|
||||||
|
```kt
|
||||||
|
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakListener
|
||||||
|
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
setupFlipper()
|
||||||
|
|
||||||
|
/*
|
||||||
|
set the flipper listener in leak canary config
|
||||||
|
*/
|
||||||
|
LeakCanary.config = LeakCanary.config.copy(
|
||||||
|
onHeapAnalyzedListener = FlipperLeakListener()
|
||||||
|
)
|
||||||
|
|
||||||
|
SoLoader.init(this, false)
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
|
||||||
|
val client = AndroidFlipperClient.getInstance(this)
|
||||||
|
/*
|
||||||
|
add leak canary plugin to flipper
|
||||||
|
*/
|
||||||
|
client.addPlugin(LeakCanary2FlipperPlugin())
|
||||||
|
client.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it!
|
||||||
@@ -41,3 +41,6 @@ project(':litho-plugin').projectDir = file('android/plugins/litho')
|
|||||||
|
|
||||||
include ':leakcanary-plugin'
|
include ':leakcanary-plugin'
|
||||||
project(':leakcanary-plugin').projectDir = file('android/plugins/leakcanary')
|
project(':leakcanary-plugin').projectDir = file('android/plugins/leakcanary')
|
||||||
|
|
||||||
|
include ':leakcanary2-plugin'
|
||||||
|
project(':leakcanary2-plugin').projectDir = file('android/plugins/leakcanary2')
|
||||||
|
|||||||
Reference in New Issue
Block a user