androidbroadcastreceiverandroid-12google-pixel

CannotDeliverBroadcastException only on Pixel devices running Android 12


I'm seeing a crash come through Crashlytics that I'm unable to reproduce or locate the cause of. The crash only ever happens on Google Pixel devices running Android 12, and the crash always happens in the background.

Image showing the crash only happens on Pixel devices running Android 12

This is the crash log from Crashlytics:

Fatal Exception: android.app.RemoteServiceException$CannotDeliverBroadcastException: can't deliver broadcast
   at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1939)
   at android.app.ActivityThread.access$2700(ActivityThread.java:256)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2190)
   at android.os.Handler.dispatchMessage(Handler.java:106)
   at android.os.Looper.loopOnce(Looper.java:201)
   at android.os.Looper.loop(Looper.java:288)
   at android.app.ActivityThread.main(ActivityThread.java:7870)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

I've looked at similar questions (like this and this) but Crashlytics is showing that these users all have plenty of free memory, and nowhere in our codebase are we calling registerReceiver or sendBroadcast so the solutions in that second question aren't any help.

Based on limited logs I'm pretty sure the crash happens when the user receives a push notification, but I have a Google Pixel 4a running Android 12 and I haven't been able to reproduce it at all when sending myself notifications.

We have a custom FirebaseMessagingService to listen for notifications that we register in the Manifest and a couple of BroadcastReceivers that listen for geofencing updates and utilize WorkManager to do some work when a transition is detected. The only thing that's changed with any of those recently is we updated WorkManager to initialize itself using Android's app startup library, but I'm not sure if that's even relevant since the crash logs give me no information, and if there was a problem with our implementation it wouldn't limit itself to just Pixel devices running Android 12.

Has anyone see this before or is there a bug exclusively on Pixel devices that run Android 12? I've spent hours digging into this and am at a complete loss.


Solution

  • With reference to Android 13, rather than earlier issues on 12, there is a Google tracker issue here, which at the time of writing is assigned but awaiting a meaningful response.

    A summary of the issue is that it only occurs on 13, and only on Pixel devices.

    CommonsWare has a blog entry on this here, and the only other clue I found anywhere was in the changelog for GrapheneOS, here, which has this line entry:

    Sandboxed Google Play compatibility layer: don't report CannotDeliverBroadcastException to the user

    We use this Play library and experience the fault, so it's possible Graphene have encountered this and had to put in an OS fix.

    Update:

    I tentatively believe that we as an app have suppressed this issue, and stopped it polluting our stats.

    We set an exception handler to absorb it, which is what GrapheneOS are doing - credit to them.

    class CustomUncaughtExceptionHandler(
        private val uncaughtExceptionHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler {
    
        override fun uncaughtException(thread: Thread, exception: Throwable) {
            if (shouldAbsorb(exception)) {
                return
            }
            uncaughtExceptionHandler.uncaughtException(thread, exception)
        }
    
        /**
         * Evaluate whether to silently absorb uncaught crashes such that they
         * don't crash the app. We generally want to avoid this practice - we would
         * rather know about them. However in some cases there's nothing we can do
         * about the crash (e.g. it is an OS fault) and we would rather not have them
         * pollute our reliability stats.
         */
        private fun shouldAbsorb(exception: Throwable): Boolean {
            return when (exception::class.simpleName) {
    
                "CannotDeliverBroadcastException" -> true
    
                else -> false
            }
        }
    }
    

    We have to operate off class name strings because the CannotDeliverBroadcastException class is hidden and not available to us.

    We install this handler early in the Application.onCreate() method, like this:

    val defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
    Thread.setDefaultUncaughtExceptionHandler(
        CustomUncaughtExceptionHandler(defaultUncaughtExceptionHandler)
    )
    

    I might be a bit premature with this, but so far this has resulted in none of these crashes appearing in Play Console. A few did appear in our other reporting platform, where what is/isn't reported has always varied.

    To be clear, I'm not suggesting this is a good approach, or one that you should necessarily take. It requires a client release and it risks masking exceptions not relating to this root cause. Fixing or ignoring this issue at the point of collection is Google's responsibility. However, it has seemingly stopped the impact on our headline reliability statistics, so I thought I'd share it as a possibility.