[DO NOT MERGE] Tmp work for exploring Q leak
A memory leak detection library for Android.
“A small leak will sink a great ship.” - Benjamin Franklin
Add LeakCanary to build.gradle:
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2' }
That's it, there is no code change needed! LeakCanary will automatically show a notification when a memory leak is detected in debug builds.
What's next?
Note: LeakCanary 2 is in alpha.
In a Java based runtime, a memory leak is a programming error that causes an application to keep a reference to an object that is no longer needed. As a result, the memory allocated for that object cannot be reclaimed, eventually leading to an OutOfMemoryError crash.
For example, an Android activity instance is no longer needed after its onDestroy() method is called, and storing a reference to that activity in a static field would prevent it from being garbage collected.
Memory leaks are very common in Android apps. OutOfMemoryError (OOM) is the top crash for most apps on the play store, however that‘s usually not counted correctly. When memory is low the OOM can be thrown from anywhere in your code, which means every OOM has a different stacktrace and they’re counted as different crashes.
When we first enabled LeakCanary in the Square Point Of Sale app, we were able to find and fix several leaks and reduced the OutOfMemoryError crash rate by 94%.
.hprof file stored on the file system. The default threshold is 5 retained instances when the app is visible, 1 otherwise..hprof file and finds the chain of references that prevents retained instances from being garbage collected (leak trace). A leak trace is technically the shortest strong reference path from GC Roots to retained instances, but that's a mouthful.To fix a memory leak, you need to look at the sub chain of possible leak causes and find which reference is causing the leak, i.e. which reference should have been cleared at the time of the leak. LeakCanary highlights with a red underline wave the references that are the possible causes of the leak.
If you cannot figure out a leak, please do not file an issue. Instead, create a Stack Overflow question using the leakcanary tag.
LeakCanary is released as several distinct libraries:
com.squareup.leakcanary:leaksentry.com.squareup.leakcanary:leakcanary-android.com.squareup.leakcanary:leakcanary-android-instrumentation.If you think a recipe might be missing or you‘re not sure that what you’re trying to achieve is possible with the current APIs, please file an issue. Your feedback help us make LeakCanary better for the entire community.
LeakSentry can be configured by replacing LeakSentry.config:
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() LeakSentry.config = LeakSentry.config.copy(watchFragmentViews = false) } }
LeakCanary can be configured by replacing LeakCanary.config:
disableLeakCanaryButton.setOnClickListener { LeakCanary.config = LeakCanary.config.copy(dumpHeap = false) }
In your application, you may have other objects with a lifecycle, such as fragments, services, Dagger components, etc. Use LeakSentry.refWatcher to watch instances that should be garbage collected:
class MyService : Service { // ... override fun onDestroy() { super.onDestroy() LeakSentry.refWatcher.watch(this) } }
com.squareup.leakcanary:leakcanary-android should only be used in debug builds. It depends on com.squareup.leakcanary:leaksentry which you can use in production to track and count retained instances.
In your build.gradle:
dependencies { implementation 'com.squareup.leakcanary:leaksentry:2.0-alpha-2' }
In your leak reporting code:
val retainedInstanceCount = LeakSentry.refWatcher.retainedKeys.size
Add the leakcanary-android-instrumentation dependency to your instrumentation tests:
androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:${leakCanaryVersion}"
Add the dedicated run listener to defaultConfig in your build.gradle:
android {
defaultConfig {
// ...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument "listener", "leakcanary.FailTestOnLeakRunListener"
}
}
Run the instrumentation tests:
./gradlew leakcanary-sample:connectedCheck
You can extend FailTestOnLeakRunListener to customize the behavior.
The activity that displays leaks comes with a default icon and label, which you can change by providing R.mipmap.leak_canary_icon and R.string.leak_canary_display_activity_label in your app:
res/
mipmap-hdpi/
leak_canary_icon.png
mipmap-mdpi/
leak_canary_icon.png
mipmap-xhdpi/
leak_canary_icon.png
mipmap-xxhdpi/
leak_canary_icon.png
mipmap-xxxhdpi/
leak_canary_icon.png
mipmap-anydpi-v26/
leak_canary_icon.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="leak_canary_display_activity_label">MyLeaks</string> </resources>
You can change the default behavior to upload the analysis result to a server of your choosing.
Create a custom AnalysisResultListener that delegates to the default:
class LeakUploader : AnalysisResultListener { override fun invoke( application: Application, heapAnalysis: HeapAnalysis ) { TODO("Upload heap analysis to server") // Delegate to default behavior (notification and saving result) DefaultAnalysisResultListener(application, heapAnalysis) } }
Set analysisResultListener on the LeakCanary config:
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() LeakCanary.config = LeakCanary.config.copy(analysisResultListener = LeakUploader()) } }
Set exclusionsFactory on the LeakCanary config to a ExclusionsFactory that delegates to the default one and then and add custom exclusions:
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() LeakCanary.config = LeakCanary.config.copy(exclusionsFactory = { hprofParser -> val defaultFactory = AndroidExcludedRefs.exclusionsFactory(AndroidExcludedRefs.appDefaults) val appDefaults = defaultFactory(hprofParser) val customExclusion = Exclusion( type = StaticFieldExclusion("com.thirdparty.SomeSingleton", "sContext"), status = Exclusion.Status.WONT_FIX_LEAK, reason = "SomeSingleton in library X has a static field leaking a context." ) appDefaults + customExclusion }) } }
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() val customLabeler: Labeler = { parser, node -> listOf("Heap dump object id is ${node.instance}") } val labelers = AndroidLabelers.defaultAndroidLabelers(this) + customLabeler val customInspector: LeakInspector = { parser, node -> with(parser) { if (node.instance.objectRecord.isInstanceOf("com.example.MySingleton")) { LeakNodeStatus.notLeaking("MySingleton is a singleton") } else LeakNodeStatus.unknown() } } val leakInspectors = AndroidLeakInspectors.defaultAndroidInspectors() + customInspector LeakCanary.config = LeakCanary.config.copy(labelers = labelers, leakInspectors = leakInspectors) } }
Yes. There are a number of known memory leaks that have been fixed over time in AOSP as well as in manufacturer implementations. When such a leak occurs, there is little you can do as an app developer to fix it. For that reason, LeakCanary has a built-in list of known Android leaks to ignore: AndroidExcludedRefs.kt.
If you find a new one, please create an issue and follow these steps:
AndroidExcludedRefs.kt. Optional: if you find a hack to clear that leak on previous versions of Android, feel free to document it.Sometimes the leak trace isn't enough and you need to dig into a heap dump with MAT or YourKit.
Here's how you can find the leaking instance in the heap dump:
leakcanary.KeyedWeakReferencekey field.KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary.referent field of that KeyedWeakReference is your leaking object.0. LeakCanary is a debug only library.
Update your dependencies to the latest SNAPSHOT (see build.gradle):
dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-3-SNAPSHOT' }
Add Sonatype's snapshots repository:
repositories {
mavenCentral()
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
LeakCanary was created and open sourced by @pyricau, with many contributions from the community.
The name LeakCanary is a reference to the expression canary in a coal mine, because LeakCanary is a sentinel used to detect risks by providing advance warning of a danger. Props to @edenman for suggesting it!
Copyright 2015 Square, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.