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.
37 lines
2.3 KiB
Plaintext
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".
|