Files
SoulStream/handoff/rounds/ins-r49-doc-singleton.txt
T
KeelrLeek 9f0601027c android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43
to 48 (each patch adversarially verified, 43's transient-notice expectation
fixed), a partial round 49, the pipeline scripts, the Android Auto design and
the icon comparison mock. Also the two harness changes that were never A/B'd —
adaptive waits, which may only shorten a declared wait and never lengthen it,
and a ship that runs the mock walk and the deploy at the same time. The A/B and
its 255-step baseline are in handoff/tools; main stays at what the phone has
actually walked.
2026-09-14 07:57:41 -05:00

37 lines
2.3 KiB
Plaintext

The session, the player, the notification provider, the audio-focus request
and the becoming-noisy receiver all live in `MediaHub` for the life of the
**process**, and `MediaService.onCreate` allocates and registers nothing. That
is not tidiness, it is androidx/media
[#3158](https://github.com/androidx/media/issues/3158) — open since
2026-04-03, still open, reproduced on media3 1.11.0: a host that binds a
`MediaLibraryService` while nothing is playing can put the service into an
`onCreate → onBind → onGetSession → onConnect → onDestroy` loop at about
thirty cycles a second, and it is 100% reproducible after a force-stop with
no existing binding, which is exactly how Android Auto and
`tools/auto-prober/` connect. The loop is Android Auto's to fix and media3
has said it will not act first, so it cannot be fixed from here — only made
harmless, by making a cycle cost nothing. Round forty-eight's
`onCreate`/`onDestroy` would have built a platform session and a legacy
browser stub thirty times a second, registered and unregistered a broadcast
receiver sixty times a second, and abandoned audio focus thirty times a
second *while music was playing*.
Three consequences, and each is a trap for the next round. `onDestroy`
calls `removeSession`, never `release()`: `removeSession` drops the session
from that instance's map and clears its listener, which is what stops a
destroyed service being called back, while `SoulStreamPlayer.markReleased()`
would latch `released = true` and leave a process-lifetime player that never
updates the card again. The session is built with
`MediaLibrarySession.Builder(applicationContext, …)` and not the
`(MediaLibraryService, …)` overload, because
`MediaSessionServiceLegacyStub.initialize()` does
`attachToBaseContext(sessionImpl.getContext())` — a session built against the
Service pins a destroyed Service as the base context of a live
`MediaBrowserServiceCompat`. And nothing releases the session at all any
more, so it and its platform `MediaSessionCompat` live until the process
dies; the walk's `one-session` check is what keeps that honest.
`MediaService.onCreate` logs `auto service created n=…` and `onDestroy` logs
`auto service destroyed`. Those two lines are the only way to count the loop
on a real phone — see "### The Auto prober".