Friday, December 21, 2012

ANR: Application Not Responding (keyDispatchingTimedOut)

This is a heads up for those Android developers that use the NDK (Native Development Kit) to write Android apps. If you are using the NDK, you are almost certainly using the glue code that Google provides in the android_native_app_glue.c file.

Chances are that in your Google Play Developer Console, you see reports of Application Not Responding (ANR keyDispatchingTimedOut.) For my app, I have 756 of these reports on an installed base of 1.5M downloads. Consulting stackoverflow or other developer groups, will invariably yield the advice not to block the main thread. However, it is easy to cause this ANR without blocking the main thread, if you are using the android_native_app_glue.c file in your project.

If two events are generated at exactly the same time, using different sources or devices, the app will freeze. You can easily produce this with a PS3 controller hooked up to your Android device and depress both analogue sticks at exactly the same time, or release them at exactly the same time. If you do this while running an NDK based app, the app will freeze and issue an ANR.

It took me a day of debugging to find a work around for this, but I am happy to report that the following change to the glue code will stop the issue from happening. What you need to do is get events from the queue repeatedly in a loop, instead of just handling a single event in process_input() function.

static void process_input(struct android_app* app, struct android_poll_source* source)
    AInputEvent* event = NULL;
    while ( AInputQueue_hasEvents( app->inputQueue ) )
        if ( AInputQueue_getEvent( app->inputQueue, &event ) >= 0 )
            int32_t handled = 0;
            uint32_t devid = AInputEvent_getDeviceId( event );
            uint32_t src   = AInputEvent_getSource( event );
            //LOGV("New input event: type=%d devid=%x src=%x\n", AInputEvent_getType(event), devid, src);
            int32_t predispatched = (AInputQueue_preDispatchEvent(app->inputQueue, event));
            if (app->onInputEvent != NULL && !predispatched) handled = app->onInputEvent(app, event);
            if (!predispatched) AInputQueue_finishEvent(app->inputQueue, event, handled);

I have reported the issue to Google.


Bram said...

I have added a 'hasEvents' check, because on some devices a TTY spew of 'Failed to receive dispatch signal.' would ensue due to EWOULDBLOCK errors.

Corey said...

Thank you the (client)' ~ Failed to receive dispatch signal. status=-11

errors were killing me, after adding your modifications to the android_native_app_glue.c all is well.

Anonymous said...

Thanks for this fix!

Krzysztof Kajdasz said...

WARNING! Be careful with this "fix"

Unfortunately it seems to cause ANR on some (or all) 2.3 devices, e.g. HTC Desire HD or Nexus One when pressing Volume Keys.

Looks like on 2.3.x you have to explicitly call AInputQueue_getEvent, otherwise it may happen that hasEvents returns false and you won't have your process_input called again and the system won't give you again an event that should be predispatched.

So, unfortunately on <4.0 devices, you have to live with the spammed log if you don't want to risk ANR-s.

My loop looks like this:

int getEventResult = -1;

while (AInputQueue_hasEvents (app->inputQueue) || (getEventResult = AInputQueue_getEvent (app->inputQueue, &event)) >= 0)
if (getEventResult < 0)
getEventResult = AInputQueue_getEvent (app->inputQueue, &event);

if (getEventResult >= 0)
getEventResult = -1;