Monday, December 31, 2012

2012: the year of the crane.

The Chinese may call it the year of the dragon. However, I hereby proclaim 2012 to be the year of the crane. And I should say: specifically the year of the Little Crane. In the year 2012 alone, The Little Crane That Could was downloaded more than 5 million times. Thank you gamers!

2012 2011
iOS 3454K 1550K
Android 1656K -
Mac 81K -

Christmas day was by far the best day for Little Crane on iOS, where it hit 23K free downloads and 1.5K purchases of the premium version that day alone.

I can also report that this year 42K people have bought the Little Crane World Editor. This makes 2012 an amazing year, exceeding the goals I set a year ago. And for the sake of completeness: Hover Biker saw 315K downloads, and A Blocky Kind of Love saw 97K downloads.

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.