Hold a decor view through its window, and the window through its screen

The decor view rule was `Activity.mDecor` / `Dialog.mDecor`, and that field
is null on a live activity more often than not. `handleResumeActivity` is
the only place that writes it, under `r.window == null`, so it is set once
per `ActivityClientRecord` rather than once per `Activity` — a screen
recreated on a configuration change is resumed, visible, and has a null
`mDecor` for the rest of its life. `PhoneWindow.mDecor` has no such gap:
every decor view in the seven real dumps here is pointed at by exactly one.

Moving the decor view under its window puts a window's 3 MB where the
window lands, so the window needs a rule too — 6 of the 16 windows in
those dumps were drawn at the top of the tree, held by a `ViewRootImpl`
or a `WindowManagerImpl` as well as by their activity. With the second
rule every decor view is under its window and every window of a screen
that is up is under that screen.

That second rule is the one whose owning reference outlives the construct
it is about: `Activity.mWindow` is set in `attach` and never cleared and
`Dialog.mWindow` is final, so a destroyed activity and a dismissed dialog
go on pointing at a window they have nothing left to do with. Owning it
anyway took the window of a leaked screen away from whatever else held it,
and turned two leaks into one on the list.

So it owns through `mWindow` and asks whether the screen is up of another
reference, one the framework does drop — the self-clearing kind every other
rule owns by, borrowed from beside the one this rule owns through. A dialog
is dropped from its own `mDecor`, which `show` sets and `dismiss` nulls; an
activity is dropped from `ActivityThread.mActivities`, which
`handleDestroyActivity` removes the record from and which the activity rule
already owns activities by. No rule reads the state of an object.

Bytes are conserved on all eight dumps in the repo, no object joins or
leaves the tree, and 0 to 16 objects of 34 K to 340 K move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 files changed
tree: 03e3089a05c4bb58fd66c33a0b9706fb5a49de99
  1. .github/
  2. config/
  3. docs/
  4. gradle/
  5. leakcanary/
  6. object-watcher/
  7. plumber/
  8. samples/
  9. shark/
  10. .editorconfig
  11. .gitattributes
  12. .gitignore
  13. AGENTS.md
  14. build.gradle.kts
  15. CLAUDE.md
  16. gradle.properties
  17. gradlew
  18. LICENSE.txt
  19. mkdocs.yml
  20. README.md
  21. settings.gradle
  22. shark-cli.sh
README.md

LeakCanary 🐤

A memory leak detection library for Android.

square.github.io/leakcanary

🙏 If you like LeakCanary you can show support by starring ⭐ this repository.

License

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.