Please thank our contributors ๐ ๐ ๐.
This release marks the start of the work on LeakCanary 3. It's not stable! While I intend to rework some APIs, I also want to minimize migration work. The best way to ensure migrations will go smoothly is to try upgrading to a 3.0 alpha and to let me know if you get any compile or runtime error.
shark-heap-growth artifact contains APIs for writing test scenarios that detect repeated heap growth.RECEIVER_EXPORTED flag when calling registerReceiver() on API 34+.LifecycleRegistry in androidx.lifecycle:lifecycle-runtime was migrated to kotlin and its mState field name changed to state which broke LeakCanary expectations.shark-cli has a new experiment neo4j command that will convert a heap dump into an embedded Neo4j database and then open Neo4j Browser to explore the heap dump.
brew install leakcanary-shark shark-cli --process com.example.app.debug neo4j

POST_NOTICICATIONS permission as well as a new LeakCanary.Config.showNotifications config to disable notifications entirely.This list reflects only a subset of all changes. For more details, see the 2.10 Milestone and the full diff.
What are some things you'd like to see in a future LeakCanary 3 version? Tell me on Twitter!
Some ideas I'm playing with:
Anyway, that‘s still very much the future, let’s talk about what's in 2.9.1 now!
I built LeakCanary to help fix leaks, but in doing so I accidentally wrote a fairly flexible heap dump parser. Since we‘re parsing the heap to find leaks anyway, we might as well report additional interesting metrics. Here’s what you'll now see in the heap dump metadata:
This is just a first pass, feedback and ideas welcome!
The heap analysis now traverses the heap dump using RandomAccessFile instead of FileChannel.transferTo() and is now 40% faster on API 23 and 20% faster on newer APIs. Also, sticky class GC roots are now deduplicated, which great reduces the memory footprint of LeakCanary on API 23 (#2324). You can read about the related investigation on py.hashnode.dev.
FailTestOnLeakRunListener, FailTestOnLeak and FailAnnotatedTestOnLeakRunListener were deprecated in LeakCanary 2.8 as they rely on hacking the Android Test library internals which have since changed, and have been replaced by LeakAssertions.assertNoLeak() and the DetectLeaksAfterTestSuccess test rule. I was initially planning of keep these around, but as I tried to increase API level coverage in LeakCanary I needed to upgrade the Android Test library to a more recent version, and the hacks now had compilation errors. So they‘re gone: #2282. If you can’t use the test rules just yet, you're welcome to copy paste the listener implementations in your own codebase.
AndroidLeakFixes.FLUSH_HANDLER_THREADS (HandlerThread can have a null Looper).WindowCallbackWrapper crashes.WindowDelegateCallback.onMenuOpened() crash.HashSet.map is null (which isn't supposed to happen, oh well, Android ๐คทโโ๏ธ).BackgroundListener$checkAppInBackground.This list reflects only a subset of all changes. For more details, see the 2.9 Milestone and the full diff.
This is a bugfix release, a quick follow up to 2.8 which had a few major issues ๐
. If you haven't yet, you should definitely read the 2.8 changelog.
Please thank @dicosta, @Goooler, @plnice, @preetha1326 for their contributions, bug reports and feature requests ๐ ๐ ๐.
This patch release fixes not 1, not 2, but 3 crashes!
LeakCanary.config crashes when AppWatcher is not installed.HashMap$Entry became HashMap$HashMapEntry (on API 25) before it finally changed to HashMap$Node.For more details, see the 2.8.1 Milestone and the full diff.
Note: please update to 2.8.1 instead.
The last release was 9 months ago. What happened?! Well, soon after releasing LeakCanary 2.7, I had my 2nd baby, a wonderful daughter ๐. Having 2 young kids leaves a lot less time available for Open Source work... but it's worth it!
โ P.Y.
Please thank @aaronweihe, @alhah, @Andre-max, @AoraMD, @BraisGabin, @breezenan, @Goooler, @iliaskomp @Jeff11, @jmnwong, @IdioticMadman, @keyur1sst, @lchen8, @leinardi, @Maragues, @mars885, @mateuszkwiecinski, @matiash, @maxxx, @preetha1326, @SimonMarquis, @slavonnet, @Sonphil, @summerlyr, @SUPERCILEX, @utwyko, @ZacSweers, @ziranshang, @zoltish for their contributions, bug reports and feature requests ๐ ๐ ๐.
๐ค Inspired by Android Studio, LeakCanary's node discovery during heap graph traversal is now abstracted away. This allows overlaying logical structure over common data structure internals.
๐ WHAT?!
๐ This means we can make known data structures look more like their APIs than their internals. For example, developers tend to think of setting a HashMap entry as map["key"] = value rather than map.table[hash("key")].next.next.next = Node(value), which is what LeakCanary would previously show in its leak traces.
Let's look at a HashMap example:
class CheckoutController { val tabs = HashMap<String, Tab>() fun addItemsTab(tab: Tab) { tabs["ItemsTab"] = tab } }
If the Tab instance holds on to a view, we might see a leak trace that would look like this:
โ ... โโ com.example.CheckoutController instance โ โ CheckoutController.tabs โโ java.util.HashMap instance โ โ HashMap.table โโ java.util.HashMap$Node[] array โ โ HashMap$Node[42] โโ java.util.HashMap$Node instance โ โ HashMap$Node.next โโ java.util.HashMap$Node instance โ โ HashMap$Node.value โโ com.example.Tab instance โ ...
With the improved data structure support, the leak trace is much clearer (also note how the ItemsTab string key is now surfaced):
โ ... โโ com.example.CheckoutController instance โ โ CheckoutController.tabs โโ java.util.HashMap instance โ โ HashMap[ItemsTab] โโ com.example.Tab instance โ ...
Another benefit of this change is that leak signatures become less dependent of the runtime, and therefore are more consistent. This is especially true for any data structure that relies on a linked list (HashMap, LinkedList, MessageQueue, ...). Currently LeakCanary supports a limited set of common data structures from Apache Harmony, Open JDK, and the Android SDK. Let me know what else you need!
LeakCanary will now detect leaks that trigger when forgetting to cancel ObjectAnimator. This new feature is enabled by the node discovery changes described above!
Let's say you accidentally start an infinite ObjectAnimator and never cancel it, like so:
class ExampleActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) findViewById<Button>(R.id.button).setOnClickListener { view -> ObjectAnimator.ofFloat(view, View.ALPHA, 0.1f, 0.2f).apply { duration = 100 repeatMode = ValueAnimator.REVERSE repeatCount = ValueAnimator.INFINITE start() } } } }
In previous releases, LeakCanary would detect that the animated view is leaking but it wouldn't be able to find the leak and instead would report it as an unreachable object: An unreachable object is still in memory but LeakCanary could not find a strong reference path from GC roots.
LeakCanary now reports the leak and adds animator state information, helping detect and fix any infinite ObjectAnimator.
โฌโโโ
โ GC Root: Thread object
โ
โโ java.lang.Thread instance
โ Leaking: NO (the main thread always runs)
โ Thread name: 'main'
โ โ Thread.threadLocals
โ ~~~~~~~~~~~~
...
โโ android.animation.ObjectAnimator instance
โ Leaking: UNKNOWN
โ mListeners = null
โ mPropertyName = null
โ mProperty.mName = alpha
โ mProperty.mType = java.lang.Float
โ mInitialized = true
โ mStarted = true
โ mRunning = true
โ mAnimationEndRequested = false
โ mDuration = 100
โ mStartDelay = 0
โ mRepeatCount = INFINITE (-1)
โ mRepeatMode = REVERSE (2)
โ โ ObjectAnimator.mTarget
โ ~~~~~~~
โฐโ android.widget.Button instance
Leaking: YES (View.mContext references a destroyed activity)
To learn more, see this AOSP issue: ObjectAnimator.mTarget weak ref creates memory leaks on infinite animators.
Previous releases of leakcanary-android-instrumentation introduced a FailTestOnLeakRunListener which could run leak detection after each UI tests. Unfortunately FailTestOnLeakRunListener relied on a hack around androidx.test internals to report failures. The internals keep changing with every androidx.test release and breaking FailTestOnLeakRunListener ๐ญ.
FailTestOnLeakRunListener is now deprecated (๐) and replaced by the DetectLeaksAfterTestSuccess test rule, which you can add to your test like any normal test rule.
Additionally, you can call LeakAssertions.assertNoLeak() from anywhere in your instrumentation tests. You can also annotate tests with @SkipLeakDetection (for that to work you'll also need to set up the TestDescriptionHolder test rule).
class CartTest { @get:Rule val rules = RuleChain.outerRule(TestDescriptionHolder) .around(DetectLeaksAfterTestSuccess()) .around(ActivityScenarioRule(CartActivity::class.java)) @Test fun addItemToCart() { // ... } @SkipLeakDetection("See #1234") @Test fun removeItemFromCart() { // ... } }
Hopefully this time we fixed everything that Android 12 broke: missing exported:true tags, missing pending intent flags, and ForegroundServiceStartNotAllowedException crashes. If not, let us know! Can't wait for Android 13 to break everything again ๐คฌ.
Running an Android Service without crashing (ForegroundServiceStartNotAllowedException...) is becoming harder with every release of Android, so I got rid of the LeakCanary heap analyzer service! Instead, LeakCanary leverages WorkManager if you already have it as a dependency. If you don't use WorkManager, then LeakCanary will fall back to using a simple thread.
Note: I recommend using at least WorkManager 2.7.0 as it adds the WorkRequest.Builder.setExpedited() API which LeakCanary leverages if available.
Switching to WorkManager also impacts the LeakCanary multi process approach, which now leverages WorkManager remote jobs. Blog on how I got this working: WorkManager multi-process for libraries.
Multi process is harder to get right so you should only use this if LeakCanary frequently runs out of memory while performing the heap analysis. Here are the updated set up steps:
leakcanary-android-process dependency and keep the leakcanary-android dependency.dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8' debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.8' }
Application classclass ExampleApplication : Application() { override fun onCreate() { if (LeakCanaryProcess.isInAnalyzerProcess(this)) { return } super.onCreate() // normal init goes here, skipped in :leakcanary process. } }
That's it! Note that event listeners (see below) related to the analysis will fire in the remote process.
LeakCanary now optionally supports the AndroidX App Startup library. All you need to do is replace the leakcanary-android dependency with leakcanary-android-startup:
dependencies { // Remove the normal leakcanary-android dependency // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8' debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.8' }
Note 1: leakcanary-android adds the code for automatic installl to leakcanary-android-core. If you‘re calling AppWatcher.manualInstall(), you can depend directly on leakcanary-android-core instead of leakcanary-android, and you won’t need the disable any automatic install.
Note 2: the same principle applies to leakcanary-object-watcher-android: it depends on leakcanary-object-watcher-android-core and adds automatic install, while leakcanary-object-watcher-android-startup leverages the App Startup library. Same for plumber-android, plumber-android-core and plumber-android-startup.
LeakCanary.Config has a new eventListeners field allowing you to react to LeakCanary's lifecycle. If you want to customize this, you most likely should be keeping the default list of listeners and add or remove from it.
For example, if you want to disable the LeakCanary toast:
LeakCanary.config = LeakCanary.config.run { copy( eventListeners = eventListeners.filter { it !is ToastEventListener } ) }
If you want to upload heap analysis results:
LeakCanary.config = LeakCanary.config.run { copy( eventListeners = eventListeners + EventListener { event -> if (event is HeapAnalysisSucceeded) { // Upload event.heapAnalysis } } ) }
Note: Leakcanary.Config.onHeapAnalyzedListener still works but is now deprecated.
Feedback welcome on this new API!
This list reflects only a subset of all changes. For more details, see the 2.8 Milestone and the full diff.
Please thank @chao2zhang, @ihrupin, @jzbrooks, @msfjarvis, @reneargento, @Unpublished for their contributions, bug reports and feature requests ๐ ๐ ๐.
In version 2.6, LeakCanary added detection of root views retained after View.onDetachedFromWindow(). This helps find more leaks, but unfortunately some Android widgets keep a detached root view around to reattach it later (e.g. spinner). App developers also sometimes do the same with dialogs, keeping a single instance around and calling show() and hide() as needed. As a result, LeakCanary would report leaks that were actually not leaks.
In version 2.7, the default behavior changed: LeakCanary will continue to detect leaks of toasts, but will ignore root views created by a PopupWindow (which is what Android widgets use). It will also ignore root views created by a dialog by default, and you can turn this back on by setting the leak_canary_watcher_watch_dismissed_dialogs resource boolean to true:
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="leak_canary_watcher_watch_dismissed_dialogs">true</bool> </resources>
This is implemented using a new Square library: Curtains.
We fixed two issues for apps that want to target Android 12:
android:exported attribute.FLAG_IMMUTABLE flag.For more details, see the 2.7 Milestone and the full diff.
Please thank @chao2zhang, @ChaosLeung, @LitterSun, @mickverm, @opatry, @Thomas-Vos, @tricknology, @rahul-a, @samoylenkodmitry, @sing0055, @ubiratansoares for their contributions, bug reports and feature requests ๐ ๐ ๐.
This Christmas Release includes several external contributions and a bunch of cool new features! ๐๐
View.onDetachedFromWindow()On Android, every displayed view hierarchy is attached to a window, whether it be the view hierarchy of an activity, a dialog, a toast or a chat head. After a view hierarchy is detached from its window, it should be garbage collected.
LeakCanary already detects leaks of activity view hierarchies because retained detached views reference their activity context and LeakCanary detects activities retained after Activity.onDestroy(). In this new release, LeakCanary will now detect the leak of a dialog view hierarchy as soon as that dialog is dismissed, or any other view that is passed to WindowManager.removeView().
Service.onDestroy()After an Android service is destroyed, it should be garbage collected. Unfortunately, the Android SDK does not provide any generic API to observe the service lifecycle. We worked around that using reflection on greylist APIs (details in #2014). Let's hope this motivates the Android team to build the APIs developers need.
With the detection of 2 new types of retained objects, we're also adding APIs to configure which watchers should be installed as well as adding filtering capabilities.
First, disable the automatic install:
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="leak_canary_watcher_auto_install">false</bool> </resources>
Then you can install LeakCanary manually. LeakCanary 2.6 comes with 4 watchers installed by default: ActivityWatcher, FragmentAndViewModelWatcher, RootViewWatcher, ServiceWatcher. Here's an example to get all the default watchers except ServiceWatcher:
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() val watchersToInstall = AppWatcher.appDefaultWatchers(application) .filter { it !is ServiceWatcher } AppWatcher.manualInstall( application = application, watchersToInstall = watchersToInstall ) } }
LeakCanary introduces a new functional (SAM) interface implemented by ObjectWatcher: ReachabilityWatcher, with a ReachabilityWatcher.expectWeaklyReachable() method that replaces the now deprecated ObjectWatcher.watch() method. You can create the default watcher instances with a custom ReachabilityWatcher that delegates to AppWatcher.objectWatcher but filters out specific instances (e.g. BadSdkLeakingFragment):
class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() val delegate = ReachabilityWatcher { watchedObject, description -> if (watchedObject !is BadSdkLeakingFragment) { AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description) } } val watchersToInstall = AppWatcher.appDefaultWatchers(application, delegate) AppWatcher.manualInstall( application = application, watchersToInstall = watchersToInstall ) } }
With these new configuration options, AppWatcher.config is now deprecated and a no-op.
The default threshold to dump the heap is 5 retained objects when the app is visible, and 1 retained object when the app is not visible. Up until now, visible meant “the app has at least one activity in started state”. In LeakCanary 2.6, the app will now be considered not visible if the device screen is off, lowering the threshold to trigger heap dumps when you turn off the device screen.
LeakCanary 2.6 introduces a new artifact: leakcanary-android-release. This artifact exposes APIs to run a heap analysis in release builds, in production.
!!! danger Everything about this is experimental. Running a heap analysis in production is not a very common thing to do, and we're still learning and experimenting with this. Also, both the artifact name and the APIs may change.
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' // NEW: LeakCanary for releases! releaseImplementation 'com.squareup.leakcanary:leakcanary-android-release:2.6' // Optional: detect retained objects. This helps but is not required. releaseImplementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:2.6' }
Here's a code example that runs a heap analysis when the screen is turned off or the app enters background, checking first if a Firebase Remote Config flag is turned on, and uploading the result to Bugsnag:
class ReleaseExampleApplication : ExampleApplication() { // Cancels heap analysis if "heap_analysis_flag" is false. private val flagInterceptor by lazy { object : HeapAnalysisInterceptor { val remoteConfig: FirebaseRemoteConfig = TODO() override fun intercept(chain: Chain): HeapAnalysisJob.Result { if (remoteConfig.getBoolean("heap_analysis_flag")) { chain.job.cancel("heap_analysis_flag false") } return chain.proceed() } } } private val analysisClient by lazy { HeapAnalysisClient( // Use private app storage. cacheDir is never backed up which is important. heapDumpDirectoryProvider = { cacheDir }, // stripHeapDump: remove all user data from hprof before analysis. config = HeapAnalysisConfig(stripHeapDump = true), // Default interceptors may cancel analysis for several other reasons. interceptors = listOf(flagInterceptor) + HeapAnalysisClient.defaultInterceptors(this) ) } private val analysisExecutor by lazy { Executors.newSingleThreadExecutor { thread(start = false, name = "Heap analysis executor") { android.os.Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND) it.run() } } } private val analysisCallback: (Result) -> Unit by lazy { val uploader = BugsnagHeapAnalysisUploader(this@ReleaseExampleApplication) { result -> if (result is Done) { uploader.upload(result.analysis) } } } override fun onCreate() { super.onCreate() // Delete any remaining heap dump (if we crashed) analysisExecutor.execute { analysisClient.deleteHeapDumpFiles() } // Starts heap analysis on background importance BackgroundTrigger( application = this, analysisClient = analysisClient, analysisExecutor = analysisExecutor, analysisCallback = analysisCallback ).start() // Starts heap analysis when screen off ScreenOffTrigger( application = this, analysisClient = analysisClient, analysisExecutor = analysisExecutor, analysisCallback = analysisCallback ).start() } /** * Call this to trigger heap analysis manually, e.g. from * a help button. * * This method returns a `HeapAnalysisJob` on which you can * call `HeapAnalysisJob.cancel()` at any time. */ fun triggerHeapAnalysisNow(): HeapAnalysisJob { val job = analysisClient.newJob() analysisExecutor.execute { val result = job.execute() analysisCallback(result) } return job } }
The Bugsnag uploader:
class BugsnagHeapAnalysisUploader(applicationContext: Application) { private val bugsnagClient: Client init { bugsnagClient = Client( applicationContext, BUGSNAG_API_KEY, DO_NOT_ENABLE_EXCEPTION_HANDLER ) bugsnagClient.setSendThreads(false) } fun upload(heapAnalysis: HeapAnalysis) { when (heapAnalysis) { is HeapAnalysisSuccess -> { val exception = HeapAnalysisReport() bugsnagClient.notify(exception) { report -> val metaData = report.error.metaData metaData.addToTab("Heap Analysis", "result", heapAnalysis.toString()) } } is HeapAnalysisFailure -> { // Please file any reported failure to // https://github.com/square/leakcanary/issues bugsnagClient.notify(heapAnalysis.exception) } } } // Exception with fake unique stacktrace to send all reports to the same error entry. class HeapAnalysisReport : Exception("Check the HEAP ANALYSIS tab") { override fun fillInStackTrace(): Throwable { stackTrace = arrayOf( StackTraceElement( "HeapAnalysisReport", "analyzeHeap", "HeapAnalysisReport.kt", 1 ) ) return this } } companion object { private const val BUGSNAG_API_KEY = YOUR_BUGSNAG_API_KEY private const val DO_NOT_ENABLE_EXCEPTION_HANDLER = false } }
We added 3 new automatic fixes for known AOSP leaks in plumber-android (details: #1993). As a reminder, plumber-android is automatically included when you add leakcanary-android, and you can add it manually for build types that don't include LeakCanary:
dependencies { // leakcanary-android adds plumber-android to debug builds debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' // This adds plumber-android to all build types implementation 'com.squareup.leakcanary:plumber-android:2.6' }
View and Context.For more details, see the 2.6 Milestone and the full diff.
Please thank @Amokrane, @Armaxis, @askont, @chao2zhang, @daniil-shevtsov, @eygraber, @msfjarvis, @mzgreen, @lchen8, @rpattabi, @sahil2441, @SylvainGirod, @vhow for their contributions, bug reports and feature requests ๐ ๐ ๐.
No one asked, so we delivered! We rewrote several core components in Shark (LeakCanary's heap analyzer) to dramatically reduce IO reads and allocations while keeping memory constant. More details on Twitter: thread by @ArtemChubaryan and thread by @Piwai.
Previously, LeakCanary computed the retained size for the leaking object (the last object in the leak trace). However, the bad reference causing objects to leak is often higher up in the leak trace and everything that it holds onto is actually leaking. So LeakCanary now computes the retained size for all the objects in the leaktrace that have a LEAKING or UNKNOWN status:
โฌโโโ โ GC Root: System class โ โโ com.example.MySingleton class โ Leaking: NO (a class is never leaking) โ โ static MySingleton.leakedView โ ~~~~~~~~~~ โโ android.widget.TextView instance โ Leaking: YES (View.mContext references a destroyed activity) โ Retaining 46326 bytes in 942 objects โ โ TextView.mContext โฐโ com.example.MainActivity instance โ Leaking: YES (Activity#mDestroyed is true) โ Retaining 1432 bytes in 36 objects
New toggle to disable heap dumping, which can be useful for QA, or when doing a product demo. LeakCanary will still show a notification when an object is retained.
The Shark CLI can now deobfuscate heap dumps:
brew install leakcanary-shark shark-cli --hprof heapdump.hprof -m mapping.txt deobfuscate-hprof
leak_canary_watcher_auto_install, leak_canary_allow_in_non_debuggable_build and leak_canary_plumber_auto_install resource booleans were meant to be public.@JvmStatic to help Java consummers.For more details, see the 2.5 Milestone and the full diff.
Please thank @0x109, @andersu, @antoniomerlin, @bishiboosh, @ckesc, @jrodbx, @LouisCAD, @marcardar, @OlivierGenez, @pyricau, @runningcode, @seljad, @worldsnas for their contributions, bug reports and feature requests.
plumber-android is a new artifact that fixes known Android leaks ๐ฝ๐งLeakCanary reports all leaks, including leaks caused by a known bug in 3rd party code that you do not have control over (reported as Library leaks). That can be annoying! LeakCanary now ships with a new dependency, plumber-android, which performs hacks at runtime to fix some of these known leaks. This releases has fixes for 11 known leaks, but this is just the beginning. Contributions welcome! ๐
Note that since the leakcanary-android dependency is usually added as a debugImplementation dependency, the plumber-android is transitively added only in debug builds, so it will not fix leaks in your release builds. You can add the dependency directly as implementation to get these fixes in release builds as well:
dependencies { implementation 'com.squareup.leakcanary:plumber-android:2.4' }
!!! warning While several of these fixes already ship in release builds of Square apps, this is the first official release of plumber-android, so you should consider it experimental.
The Shark CLI can now be installed via Homebrew
brew install leakcanary-shark
You can then look for leaks in apps on any connected device, for example:
$ shark-cli --device emulator-5554 --process com.example.app.debug analyze
If you set up LeakCanary to report test failures when detecting leaks in instrumentation tests, it now works with Android Test Orchestrator as well. No change required, LeakCanary will automatically detect thatAndroid Test Orchestrator is running and hook into it.
master branchThe branch name master comes from the master / slave terminology. We renamed the default branch to main, a small step towards making the LeakCanary community a safer space. Here's a good thread on this topic.
AppWatcher.Config.enabled is now deprecated.For more details, see the 2.4 Milestone and the full diff.
This is a minor release on the feature front, but a large release on the documentation front!
Many thanks to @adamfit, @Amokrane, @Armaxis, @artnc, @burakeregar, @ClaasJG, @clementcontet, @ckesc, @cketti, @fbenbassat, @Guneetgstar, @Igorxp5, @JLLeitschuh, @KidAndroid, @ligi, @mzgreen, @pyricau, @sprintuu, @tevjef, @thrlr123 for the contributions, bug reports and feature requests.
We asked for help and immediately started seeing more contributions. Thanks all! Check out the How to help page.
Despite the documentation insisting on using debugImplementation, we‘ve seen apps ship LeakCanary in release builds. Mistakes happen, so we’ve made that mistake harder to miss by making LeakCanary crash when included in release builds.
Learn More: LeakCanary in release builds.
The doc site content has changed quite a bit! We applied advice from Google's tech writing guide. If you've been confused by Library Leaks before, check out see the new Categorizing leaks section. Take a look around, let us know what you think.
For more details, see the 2.3 Milestone and the full diff.
We've got some good stuff for the first release of the decade!
Many thanks to @AndroidInternal, @Armaxis, @lic2050, @mzgreen, @orenktaboola, @personshelldon, @Plastix, @pyricau for the contributions, bug reports and feature requests.
Android ViewModels are really cool! Their lifecycle is much nicer than fragments or activities, but sometimes mistakes happen. LeakCanary will now automatically detect ViewModel leaks and report any ViewModel instance retained after its onCleared() method was called.
LeakCanary is finally coming to big screens near you! Best part - no additional setup is required, just enable it like you would for a mobile device. Now whenever there's a leak - you will see a helpful Toast appear with all the details. Make sure to check out our new Android TV section and chill!
It was brought to our attention that configuring LeakCanary and AppWatcher was a miserable experience from Java code. Well, not anymore!
Now you can use LeakCanary.Config.Builder and AppWatcher.Config.Builder to have idiomatic Java when updating the configurations. For example:
LeakCanary.Config config = LeakCanary.getConfig().newBuilder() .retainedVisibleThreshold(3) .computeRetainedHeapSize(false) .build(); LeakCanary.setConfig(config);
If you notice any other problems when using LeakCanary from Java, please file an issue! We take Java-interop seriously and will be happy to improve LeakCanary's API!
For more details, see the 2.2 Milestone and the full diff.
A special New Year's Eve release ๐ฅณ, the next release will be in another decade ๐!
Many thanks to @adamfit, @alexander-smityuk, @Armaxis, @BraisGabin, @devism, @ditclear, @jrodbx, @jstefanowski, @Maragues, @mzgreen, @pyricau for the contributions, bug reports and feature requests.
It's fairly common for teams to have a QA build that is tested before making the release build. Usually that build will be obfuscated (via Proguard or R8), but also add LeakCanary to detect leaks during QA. This leads to obfuscated leak traces, which are hard to understand ๐คฏ. Check out our new Gradle deobfuscation plugin and rejoice!
In 2.0 we changed the LeakCanary UI and UX, and built a foundation on which 2.1 extends.
New tag that will show until you open up a leak. There’s also a Library Leak tag for leaks that are known to be caused by a bug in the Android Framework or Google libraries, and the library leak description now shows up in the UI.FontsContract class and ExampleApplication instance above.View instances is now displayed in the leak trace. You shouldn't look at the implementation.โโ android.widget.TextView instance โ View.mID = R.id.helper_text
Leak and LeakTrace classes have significantly changed, e.g. all LeakTrace instances with an identical signature are grouped under the same Leak object. Despite these breaking changes, this release version is a minor update. Oh noes, what about semantic versioning ๐ฑ? Ask Don Quixote.LeakCanary.config = LeakCanary.config.copy( leakingObjectFinder = FilteringLeakingObjectFinder( AndroidObjectInspectors.appLeakingObjectFilters ) )
org.junit.Test is in the classpath. Unfortunately, some apps ship Junit in their app debug classpath (e.g. when using OkHttp MockWebServer). You can now customize which class is used to detect tests:<resources> <string name="leak_canary_test_class_name">assertk.Assert</string> </resources>
Shark CLI was rewritten on top of Clikt:
$ shark-cli Usage: shark-cli [OPTIONS] COMMAND [ARGS]... ^`. .=""=. ^_ \ \ / _ _ \ \ \ { \ | d b | { \ / `~~~--__ \ /\ / { \___----~~' `~~-_/'-=\/=-'\, \ /// a `~. \ \ / /~~~~-, ,__. , /// __,,,,) \ | \/ \/ `~~~; ,---~~-_`/ \ / \/ / / '. .' '._.' _|`~~`|_ /|\ /|\ Options: -p, --process NAME Full or partial name of a process, e.g. "example" would match "com.example.app" -d, --device ID device/emulator id -m, --obfuscation-mapping PATH path to obfuscation mapping file --verbose / --no-verbose provide additional details as to what shark-cli is doing -h, --hprof FILE path to a .hprof file --help Show this message and exit Commands: interactive Explore a heap dump. analyze Analyze a heap dump. dump-process Dump the heap and pull the hprof file. strip-hprof Replace all primitive arrays from the provided heap dump with arrays of zeroes and generate a new "-stripped.hprof" file.
There's a new interactive command which enables exploring the heap dump from the command line:
$ shark-cli -h heapdump.hprof interactive Enter command [help]: help Available commands: analyze Analyze the heap dump. class NAME@ID Show class with a matching NAME and Object ID. instance CLASS_NAME@ID Show instance with a matching CLASS_NAME and Object ID. array CLASS_NAME@ID Show array instance with a matching CLASS_NAME and Object ID. ->instance CLASS_NAME@ID Show path from GC Roots to instance. ~>instance CLASS_NAME@ID Show path from GC Roots to instance, highlighting suspect references. help Show this message. exit Exit this interactive prompt.
We're currently exploring the idea of adding support for SQL queries, feedback welcome!
For more details, see the 2.1 Milestone and the full diff.
In the past 7 months, LeakCanary went through 3 alphas and 5 betas, encompassing 23 contributors over 493 commits, 35826 insertions and 10156 deletions.
YES! LeakCanary 2 is so much better, it might make you excited when you see a new memory leak. Follow the upgrade guide, you won't regret it!
Everything. The LeakCanary codebase went from ~6000 lines of Java to ~16000 lines of Kotlin, excluding comments & blanks.
!!! question “Isn't Kotlin supposed to drastically reduce the amount of boilerplate code?” Absolutely! And it did. But then, we wrote more code. LeakCanary used to depend on HAHA, a repackaging of perflib, the heap dump parser used by Android Studio. Unfortunately perflib was slow and used too much memory, so LeakCanary now includes its own heap dump parser: Shark. The extra code comes from Shark, but also from having a lot more automated tests, and an improved UI layer.
One major difference: when the app is in foreground, LeakCanary 2 will not trigger on every retained instance. Instead it will wait until the app goes in background or to reach a threashold of 5 retained instances in foreground. The analysis will then find all the leaks at once, and group identical leaks in the results UI. Please read the Fundamentals section to learn more!
Many thanks to @AndreasBoehm, @jrodbx, @pyricau for the contributions, bug reports and feature requests.
For more details, see the 2.0 Milestone and the full diff.
leak_canary_about_message string triggered multiple substitutions warning) #1630Many thanks to @DanEdgarTarget, @msfjarvis, @PaulWoitaschek, @pyricau, @ZacSweers for the contributions, bug reports and feature requests.
For more details, see the 2.0-beta-5 Milestone and the full diff.
Many thanks to @Armaxis, @BraisGabin, @bric3, @elihart, @fernandospr, @flickator, @gabrysgab, @JorgeDLS, @lannyf77, @msfjarvis, @mzgreen, @ozmium, @PaulWoitaschek, @pyricau, @shelpy, @vRallev, @ZacSweers for the contributions, bug reports and feature requests.
For more details, see the 2.0-beta-4 Milestone and the full diff.
JavaLocalPattern not matching on Lollipop #1524Many thanks to @Armaxis, @elihart, @emartynov, @hmcgreevy-instil, @pyricau for the contributions, bug reports and feature requests.
For more details, see the 2.0-beta-3 Milestone and the full diff.
Many thanks to @kolphi, @pyricau, @ZacSweers for the contributions, bug reports and feature requests.
For more details, see the 2.0-beta-2 Milestone and the full diff.
shark-cli analyze-process com.example.myapp from your computer.Many thanks to @arctouch-carlosottoboni, @jemaystermind, @kushagrakumar27, @pyricau, @snkashis for the contributions, bug reports and feature requests.
For more details, see the 2.0-beta-1 Milestone and the full diff.
RefWatcher.retainedInstances which returns the instances that are currently considered retained.LeakCanary.Config.maxStoredHeapDumps (default 7) and LeakCanary.Config.requestWriteExternalStoragePermission (default false). LeakCanary won't ask for the external storage permission anymore by default.LeakCanary.Config.exclusionsFactory replaced with LeakCanary.Config.knownReferences (simpler use), LeakCanary.Config.leakInspectors and LeakCanary.Config.labelers merged into LeakCanary.Config.leakTraceInspectors which provides access to the entire leak trace as well as a new graph oriented API that replaces the low level hprof parser API.RefWatcher.hasRetainedReferences => RefWatcher.hasRetainedInstances, RefWatcher.retainedReferenceCount => RefWatcher.retainedInstanceCount, RefWatcher.hasWatchedReferences => RefWatcher.hasWatchedInstances, RefWatcher.removeKeysRetainedBeforeHeapDump => RefWatcher.removeInstancesRetainedBeforeHeapDump, RefWatcher.clearWatchedReferences => RefWatcher.clearWatchedInstances.Many thanks to @1step2hell, @afollestad, @ansman, @bjdodson, @BraisGabin, @EBfVince, @jaredsburrows, @pforhan, @pyricau, @tellypresence, @wiyarmir for the contributions, bug reports and feature requests.
For more details, see the 2.0-alpha-3 Milestone and the full diff.
Many thanks to @forrestbice, @Foso, @Goddchen, @marcosholgado, @orionlee, @pyricau, @satoshun, @ZacSweers for the contributions!
For more details, see the 2.0-alpha-2 Milestone and the full diff.
leakcanary-android-perflib but will be removed after alpha.Many thanks to @BraisGabin, @colinmarsch, @jrodbx, @flickator, @JakeWharton, @pyricau, @WhatsEmo for the contributions!
For more details, see the 2.0-alpha-1 Milestone and the full diff.
LeakCanary.isInAnalyzerProcess now correctly returns true in the analyzer process prior to any first leak (could be triggered by starting the leak result activity).Many thanks to @KMaragh, @pyricau, @SebRut for the code contributions!
For more details, see the 1.6.3 Milestone and the full diff.
Many thanks to @fractalwrench, @ZacSweers, @Goddchen, @igokoro, @IlyaGulya, @JakeWharton, @javmarina, @jokermonn, @jrodbx, @Parseus, @pyricau, @scottkennedy for the code contributions!
AbstractAnalysisResultService should now override onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) instead of onHeapAnalyzed(@NonNull HeapDump heapDump, @NonNull AnalysisResult result)For more details, see the 1.6.2 Milestone and the full diff.
Many thanks to @AdityaAnand1, @alhah, @christxph, @csoon03, @daqi, @JakeWharton, @jankovd, @jrodbx, @kurtisnelson, @NightlyNexus, @pyricau, @SalvatoreT, @shmuelr, @tokou, @xueqiushi for the code contributions!
Note: we made a 1.6 release but quickly followed up with 1.6.1 due to #1058.
LeakCanary.installedRefWatcher()AnalysisResult.leakTraceAsFakeException() returns an exception that can be used to report and group leak traces to a tool like Bugsnag or Crashlytics.InstrumentationLeakDetector and FailTestOnLeakRunListener APIs for detecting leaks in instrumentation tests.Reachability.Inspector and RefWatcherBuilder.stethoscopeClasses() API to establish reachability and help identify leak causes.AndroidRefWatcherBuilder.watchActivities(false), watching fragments can be disabled with AndroidRefWatcherBuilder.watchFragments(false)LeakCanary.setDisplayLeakActivityDirectoryProvider() is deprecated and replaced with LeakCanary.setLeakDirectoryProvider()RefWatcherBuilder.computeRetainedHeapSize() API to enable the computing of the retained heap size (off by default).For more details, see the 1.6.1 Milestone and the full diff.
For more details, see the full diff.
For more details, see the full diff.
For more details, see the full diff.
LeakCanary.isInAnalyzerProcess() to the no-op jarLeakCanary.refWatcher().For more details, see the full diff.
HeapAnalyzer.findTrackedReferences() method for headless analysis when you have no context on what leaked.LeakCanary.isInAnalyzerProcess() to the no-op jarLeakCanary.refWatcher() which returns an AndroidRefWatcherBuilder that extends RefWatcherBuilder and lets you fully customize the RefWatcher instance.LeakCanary.install(Application, Class) and LeakCanary.androidWatcher(Context, HeapDump.Listener, ExcludedRefs).R.integer.leak_canary_max_stored_leaks and R.integer.leak_canary_watch_delay_millis, those can now be set via LeakCanary.refWatcher().LeakDirectoryProvider API to centralize all file related responsibilities.RefWatcher is now constructed with a WatchExecutor which executes a Retryable, instead of an Executor that executes a Runnable.HeapDumper.NO_DUMP was renamed HeapDumper.RETRY_LATERleak_canary_ instead of __leak_canary#161Throwable instead of an Exception. Main goal is to catch and correctly report OOMs while parsing.ExcludedRefs fields.ExcludedRef entry can now be ignored entirely or “kept only if no other path”.ExcludedRef and AndroidExcludedRefs are customizable: #12 #73.minSdkVersion from 9 to 8: #57.LeakCanary.leakInfo(): #49.leakcanary-android-no-op is lighter, it does not depend on leakcanary-watcher anymore, only 2 classes now: #74.__leak_canary_heap_dump_toast.xml (e.g. you could make it an empty layout).android.permission.WRITE_EXTERNAL_STORAGE to leakcanary-android artifact.LeakCanary.androidWatcher() parameter types have changed (+ExcludedRefs).LeakCanary.leakInfo() parameter types have changed (+boolean)ExcludedRef is now serializable and immutable, instances can be created using ExcludedRef.Builder.ExcludedRef is available in HeapDumpAndroidExcludedRefs is an enum, you can now pick the leaks you want to ignore in AndroidExcludedRefs by creating an EnumSet and calling AndroidExcludedRefs.createBuilder().AndroidExcludedRefs.createAppDefaults() & AndroidExcludedRefs.createAndroidDefaults() return a ExcludedRef.Builder.ExcludedRef moved from leakcanary-analyzer to leakcanary-watcherInitial release.