androidx / androidx/media

MediaLibraryService enters tight create/destroy loop when Android Auto binds while no playback is ongoing

Open
#3,158 15 comments 1 reaction 1 assignee View on GitHub

@marcbaechinger is already working on this.

Since Apr 7, 2026.

bug
Dominant language
Java
Stars
3k
Forks
955
Avg merge
12d 14h
Merged PRs (30d)
2

Description

Version

Media3 1.7.1 (same as 1.6.1)

More version details

This started happening recently 2-3 weeks ago and was only noticed because some of our users were rate limited due to excessive api calls which are being done when service is created to generate a queue from last listening session.

Devices that reproduce the issue
  • Tested on Samsung Galaxy Z Flip5 (Android 16) and Pixel 5 (Android 14)
  • Android Auto connected via Desktop Head Unit (DHU)
  • Also reproducible with a simple instrumentation test using bindService/unbindService
Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

When Android Auto connects to a phone, it discovers media apps by binding to their MediaLibraryService using the legacy android.media.browse.MediaBrowserService action. If no playback is ongoing, the following loop occurs at ~30-35ms per cycle:

onCreate → onBind → onGetSession → onConnect → onPostConnect → onUnbind → onDestroy → (repeat)

No onStartCommand is ever called — the service is started purely by binding (BIND_AUTO_CREATE). When the external client unbinds, the service has zero remaining clients and isPlaybackOngoing is false, so Android destroys it. The external client (Android Auto) detects the service disappeared and rebinds immediately, restarting the cycle.

This loop runs indefinitely at ~28 cycles/second. Each cycle allocates a new ExoPlayer, MediaSession, and runs the full onCreate initialization. In production, this leads to:

  • Resource exhaustion: leaked BroadcastReceivers/ContentObservers crash the app with "too many broadcast receivers registered"
  • Backend overload: any network calls triggered during onCreate/initializeBoundServices fire every ~35ms, overwhelming APIs with HTTP 429 responses
Minimal reproduction (no car needed)

The following instrumentation test simulates exactly what Android Auto does — bind via the legacy action, then unbind:

@HiltAndroidTest // or however your service DI is set up
@RunWith(AndroidJUnit4::class)
class MediaServiceBindLoopTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Before
    fun setup() { hiltRule.inject() }

    @Test
    fun reproduceBindUnbindLoop() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        val bindIntent = Intent("android.media.browse.MediaBrowserService").apply {
            component = ComponentName(context, YourMediaLibraryService::class.java)
        }

        for (i in 1..300) {
            val connection = object : ServiceConnection {
                override fun onServiceConnected(name: ComponentName?, service: IBinder?) {}
                override fun onServiceDisconnected(name: ComponentName?) {}
            }

            if (context.bindService(bindIntent, connection, Context.BIND_AUTO_CREATE)) {
                Thread.sleep(40) // Simulates AA's quick probe timing
                context.unbindService(connection)
            }
            Thread.sleep(10)
        }
    }
}

Sadly I couldn't reproduce this loop with Desktop Head unit or physical AA device

Logs from production crash (sanitized)

From a Crashlytics session showing the loop. All timestamps are within the same second — 28 full cycles captured in the ring buffer:

13:33:27  MEDIA_SERVICE | onCreate
13:33:27  MEDIA_SERVICE | onBind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onGetSession | package = android.media.session.MediaController, uid = -1
13:33:27  MEDIA_SERVICE | onConnect | sessionPlayer = ForwardingPlayer@49f1214, controller = ControllerInfo {pkg=com.example.app, uid=10411}
13:33:27  MEDIA_SERVICE | onPostConnect
13:33:27  MEDIA_SERVICE | onUnbind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onDestroy
13:33:27  PlayerQueueController | unbind player: ForwardingPlayer@49f1214
13:33:27  -- cycle repeats immediately --
13:33:27  MEDIA_SERVICE | onCreate
13:33:27  MEDIA_SERVICE | onBind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onGetSession | package = android.media.session.MediaController, uid = -1
13:33:27  MEDIA_SERVICE | onConnect | sessionPlayer = ForwardingPlayer@8081986, controller = ControllerInfo {pkg=com.example.app, uid=10411}
13:33:27  MEDIA_SERVICE | onPostConnect
13:33:27  MEDIA_SERVICE | onUnbind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onDestroy
13:33:27  PlayerQueueController | unbind player: ForwardingPlayer@8081986
13:33:27  -- cycle repeats immediately --
13:33:27  MEDIA_SERVICE | onCreate
13:33:27  MEDIA_SERVICE | onBind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onGetSession | package = android.media.session.MediaController, uid = -1
13:33:27  MEDIA_SERVICE | onConnect | sessionPlayer = ForwardingPlayer@a52d2e6, controller = ControllerInfo {pkg=com.example.app, uid=10411}
13:33:27  MEDIA_SERVICE | onPostConnect
13:33:27  MEDIA_SERVICE | onUnbind | intent = Intent { act=android.media.browse.MediaBrowserService }
13:33:27  MEDIA_SERVICE | onDestroy
13:33:27  PlayerQueueController | unbind player: ForwardingPlayer@a52d2e6

Key observations:

  • No onStartCommand in any cycle — this is a bind-only start
  • onGetSession uid = -1 — the platform probe, not a real client
  • onConnect controller is the app's own compat controller (created internally by Media3 when a legacy MediaBrowserService bind arrives) — not an external MediaController
  • New player instance every cycle (different object hashes: @49f1214, @8081986, @a52d2e6)
  • All within the same second — ~35ms per full cycle
Expected result

When an external client binds and quickly unbinds while no playback is ongoing, the service should not enter a tight create/destroy loop

Actual result

Service ends in tight create/destroy loop when connecting with AA which leads to crash due to too many broadcast receivers

Fatal Exception: java.lang.IllegalStateException: Too many receivers, total of 1000, registered for pid: 3226, callerPackage: com.example.app
Top 10 actions:
  android.media.AUDIO_BECOMING_NOISY: 990 receivers
Media

Not applicable

Bug Report
  • You will email the zip file produced by adb bugreport to android-media-github@google.com after filing this issue.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.