Categories
Geek / Technical

Fixed SDL2 Android App Issue When Suspending and Resuming

Months ago, I needed to update the SDL2 libraries I was using in order to gain access to a new piece of functionality in SDL2 that lets me load SVG vector graphic file and create textures out of them. But then I found a problem was introduced. The app would hang whenever I would leave it to look at a different app. I posted about it on the libSDL Discourse site, but I did not receive a response. I finally had a chance to investigate this issue and I think I have solved it.

When an SDL2-based Android app needs to suspend and resume, there are events that need to be handled immediately, before your main event loop: SDL_APP_WILLENTERBACKGROUND and SDL_APP_DIDENTERFOREGROUND.

So you create an event filter, register it with SDL_SetEventFilter(), and it is here that you need to handle things like music. If your app enters the background, but music is still playing, the app will crash. So, in your event filter, you would have code that looks something like the following:

int EventHandler::eventFilter(void * userData, SDL_Event * event)
{
    int whatToDoWithThisEvent(ADD_TO_QUEUE);

    IHardwareLayer * hardwareLayer = static_cast<IHardwareLayer *>(userData);
    if (hardwareLayer != 0) 
    { 
        switch(event->type)
        {
            case SDL_APP_WILLENTERBACKGROUND:
                {
                    hardwareLayer->pauseMusic();
                    whatToDoWithThisEvent = DO_NOT_ADD_TO_QUEUE;
                }
                break;

            case SDL_APP_DIDENTERFOREGROUND:
                { 
                    hardwareLayer->resumeMusic();
                    whatToDoWithThisEvent = DO_NOT_ADD_TO_QUEUE;
                }
                break;
        }
    }

    return whatToDoWithThisEvent;
}

In my case, I have a wrapper around SDL that I call IHardwareLayer, and it will delegate the calls to SDL as well as do some other useful things.

The above code worked fine. Before I added this event filtering, my music-playing Android app would become unresponsive whenever the app would get suspended. With SDL2 and SDL2_mixer, the music plays on a separate thread, but most of the time you don’t need to know about it. In this case, however, you need to stop the music before your app gets suspended in order to prevent it from trying to play music in a bad state.

Ok, great! But then I updated my libraries in order to handle the ability to load SVGs, and the above code started failing. Except suspending worked just fine. It was the resume functionality that was broken now. How bizarre!

So the symptom is that when the app is suspended, everything is fine, but bringing the app back to the foreground would cause it to become unresponsive.

By adding logs and using adb logcat, I could trace the code all the way until it called Mix_ResumeMusic(), and it never returned from the call. What gives?

After trying a number of different approaches, including updating SDL2_mixer to the latest release, I finally tried to resume the music in the main event loop.
I presume the issue has to do with threading, as I learned I couldn’t call functions that would let me query the pause state of the music in the event filter without the app becoming unresponsive.

The way the event filter works, it gets called before your main event loop, giving you a chance to handle an event beforehand. You can, of course, ignore an event, and in my code above, the default return value ADD_TO_QUEUE is an int that has a value of 1, which has the effect of allowing the event to get handled later in my main event loop when I call SDL_PollEvent().

So I changed the above code so that it no longer tries to resume music and it no longer returns DO_NOT_ADD_TO_QUEUE (otherwise known as the return value of 0) when the filter receives the event SDL_APP_DIDENTERFOREGROUND. Then I added the following code to my main event loop, which is in my HardwareLayer wrapper class:

SDL_Event event;
while (1 == SDL_PollEvent(&event))
{
    switch (event.type)
    {
        case SDL_APP_DIDENTERFOREGROUND:
            {
                resumeMusic(); 
            }
            break;

...

So now I pause the music when handling the SDL_APP_WILLENTERBACKGROUND event in the event filter, and I resume music once my app is up and running the main event loop again. No more crashes or unresponsive app!

I am a bit bothered by the lack of symmetry in terms of how to handle suspending and resuming an app, but software development is often about compromise. B-)