Files
amethyst/nappletHost
Vitor PamplonaandClaude Opus 5 018c693077 fix: release the broker Messenger so a destroyed sandbox Activity can be freed
A full-screen napplet/browser surface ran onDestroy cleanly and was gone from
ActivityManager, yet the :napplet process kept the Activity, its window and its
WebView alive through repeated forced GCs. A heap dump gives the chain:

  ROOT(JNI_GLOBAL) android.os.Handler$MessengerImpl
    -> MessengerImpl.this$0  = android.os.Handler
    -> Handler.mCallback     = <lambda>
    -> lambda.f$0            = NappletBrowserActivity

`replyMessenger = Messenger(Handler(mainLooper, ::onBrokerReply))` makes the
Activity the handler's callback (a bound method reference captures `this`), and
a Messenger sent over IPC is a binder — so while the broker holds it, ART keeps
a JNI global reference to that Handler here in the sandbox. One retained
Messenger therefore pinned Activity -> PhoneWindow -> DecorView -> WebView, and
no GC in the sandbox could reclaim it; only killing the process could.

The broker keeps replyTo in long-lived structures (incBus subscriptions,
liveSubscriptions, identityWatch, foregroundLeases) and onDestroy only called
unbindService, which releases none of them.

Fix both halves:
  - MSG_RELEASE_CLIENT, sent first thing in onDestroy, so the broker drops the
    Messenger's inc-bus subscriptions and this surface's foreground lease. It
    goes directly on brokerMessenger rather than through sendToBroker, which
    queues while unbound — a queued release would never be sent.
  - The reply handler now holds the Activity through a WeakReference, so even a
    broker that never processes the release cannot pin a surface again.

Verified on an emulator: opening one full-screen page and pressing back left
Activities:1 WebViews:2 across three forced GCs before, and settles to
Activities:0 WebViews:1 after. Heap dump: NappletBrowserActivity instances drop
from 13 (the leaked Activity plus its captured lambdas) to 1 — the Companion,
which is a static singleton and correctly retained.

Note it takes two GC cycles to settle; one of the reference paths runs through
a Cleaner chain, so a single forced GC still shows the old numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:35:33 -04:00
..