Skip to content

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

@JaCobbMedia

Description

@JaCobbMedia

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

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions