Skip to content

Hold a decor view through its window, and the window through its screen - #2943

Closed
pyricau wants to merge 1 commit into
owner-rules-as-codefrom
window-owns-the-decor-view
Closed

Hold a decor view through its window, and the window through its screen#2943
pyricau wants to merge 1 commit into
owner-rules-as-codefrom
window-owns-the-decor-view

Conversation

@pyricau

@pyricau pyricau commented Aug 5, 2026

Copy link
Copy Markdown
Member

Stacked on #2941. The question this answers: is there always a window when there's a decor view, and if so, should the rule be PhoneWindow.mDecor rather than Activity.mDecor?

Is there always a window?

Yes, for a decor view: every one in the seven real dumps in the repo is pointed at by exactly one PhoneWindow.mDecor (1 of 1 in compose_leak.hprof, 3 of 3 in leak_asynctask_m.hprof, and so on).

Not for a root view, which is the distinction that matters: 14 of the 15 root views in compose_leak.hprof, 9 of the 10 in leak_asynctask_m.hprof and 22 of the 23 in gcroot_unknown_object.hprof have no window anywhere. They're Toast views, PopupWindow.mContentView, TextView$MagnifierViews, Toolbar.mNavButtonView image buttons and fragment roots, each held the ordinary way by whatever made it. So the rule is window → decor view, and the rest need nothing.

Why Activity.mDecor had to go

ActivityThread.handleResumeActivity is the only place that writes it, guarded by if (r.window == null && !a.mFinished && willBeVisible) — so it is set once per ActivityClientRecord, not once per Activity. A screen recreated on a configuration change is resumed, visible, and has a null mDecor for the rest of its life. It also inverted the nesting where it was set: in leak_asynctask_m.hprof a PhoneWindow was drawn under its own DecorView, through DecorView.this$0.

Why the window then needs a rule of its own

A window has 6 to 9 referrers in these dumps. Most are its own inner classes and its decor's, but the external ones — WindowManagerImpl.mParentWindow, ActivityThread$ActivityClientRecord.window, DecorView.mWindow reached from a ViewRootImpl — put 6 of the 16 windows at the top of the tree. Moving the decor view under the window makes that worse than it was, because the hierarchy now floats with it: gcroot_unknown_object.hprof's PhoneWindow went from 14 KB at the root to 3.09 MB there, large-dump.hprof's from 12 KB to 2.14 MB. With Activity.mWindow / Dialog.mWindow owning it, every decor view in every dump is under its window and every window of a screen that is up is under that screen.

The one state check

ActivityWindowRule reads Activity.mDestroyed and Dialog.mDecor, which no other rule here needs. It has to: 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 to do with. Owning it regardless took the window of a leaked screen away from whatever else was holding it and turned two leaks into one on the list — the reference that would still have been there after the activity was fixed stopped being reported. HeapLeaksTest covers that, and notes/dominator-tree.md now records it as the test to apply to a new rule.

Conserved

Bytes conserved on all eight dumps, no object joins or leaves the tree, 0 to 16 objects of 34 K to 340 K move. shark-explorer-core:check and shark-explorer-app:check pass.

🤖 Generated with Claude Code

@pyricau
pyricau force-pushed the owner-rules-as-code branch from 47066fd to c4e8739 Compare August 6, 2026 05:11
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 here that reads the state of its owner, and
it has to: `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 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.

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>
@pyricau

pyricau commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

🤖 Folded into #2941 — the two commits are one change, so they're one PR now. The state read this branch had is gone: ActivityWindowRule asks ActivityThread.mActivities whether the framework is still running the screen instead of reading Activity.mDestroyed.

@pyricau pyricau closed this Aug 6, 2026
@pyricau
pyricau deleted the window-owns-the-decor-view branch August 6, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant