Cleanly shutting down Android app

What is the recommended way of shutting down a Juce Android app? I’m updating an app where the Back button was closing it (a rather standard behaviour) through the default onBackPressed() method, but this now generates an exception in a juce_android_SystemStats callback.

To be sure the problem was not in my code, I tried quitting from the JuceDemo app, and I get the same problem.

Do you mean shutting the app completely or just putting it into background by going to the home screen?

If you want to just move the app to the background programmatically, you can call juce::Process::hide().

If you want to close the app completely from your code, then juce::ApplicationBase::quit() is the function you want.

I’ve just checked and calling juce::ApplicationBase::quit() causes the app to crash indeed. I’ll have a look.

On a side note:

If you press a home button or a back button, to get to the home screen, you should expect juce::ApplicationBase::suspended() callback to be called. The app will be moved to the background and a user will still be able to go back to it. Then, you will receive juce::ApplicationBase::resumed() whenever the user goes back to your app.

If you however fully close the app by first showing the list of all running applications and then swiping it away (or pressing the X button in top right corner), then you should expect to get juce::ApplicationBase::suspended() callback, followed by juce::ApplicationBase::shutdown().

2 Likes

Thanks a lot for this very detailed feedback. I meant to trigger the shutdown from the Java side. From what I understand, letting the default behavior of onBackPressed take its course, it would trigger finish() on the activity, which in turn would close the JNI lib, though that seems to trigger the exception I mentioned.

I don’t want the app to move to background. That’s already working through the Home button. Back button usually closes the app.

Hey,

In general I would discourage from calling Java directly, unless one finds a solid reason to do so. Instead, calling JUCE’s C++ APIs is a better option because calling JUCE code will work across all supported platforms. And more importantly, internal code in JUCE, like the one talking to Java, may change at any point, breaking any client code relying on those JUCE internals.

In fact, JUCEApplicationBase::quit() was already doing what you intended to do, it was resulting in the following code being called:

void MessageManager::stopDispatchLoop()
{
    struct QuitCallback  : public CallbackMessage
    {
        QuitCallback() {}

        void messageCallback() override
        {
            android.activity.callVoidMethod (JuceAppActivity.finish);
        }
    };

    (new QuitCallback())->post();
    quitMessagePosted = true;
}

so calling JUCEApplicationBase::quit() you would give you the same result as calling Java’s Activity::finish().

I have fixed the original crash here and I have also changed the behaviour of JUCEApplicationBase::quit() to really quit the app (same as we do on iOS) here as otherwise it was confusing JUCE and assertions on startup would be hit. Note that on Android 5.0 (API 21) calling JUCEApplicationBase::quit() will close the app and make it disappear from recent apps list. Prior to Android 5.0, the app will be closed, but it will still appear in recent tasks list - when a user presses on it, a new fresh instance of the app will be created.

Thanks a lot for fixing this. I’ll give it a try and let you know in case I face any further problem.

if you read my request/replies, I am not trying to do anything specific, just want the Android back button to trigger shutdown of the app as it should (and was apparently doing, except for that bug).

I’m all for “keeping to JUCE code”, but when the action is triggered in an Android Java app, the real hack would be to bypass that, trigger the JUCE app shutdown and then from JUCE shutdown the Java activity imho.

Best wishes,

Mariano

Hi Mariano,

When the back button is pressed, we just get a callback in Java’s Activity that the back button was pressed, Java does not attempt to close the app by itself. We then get this callback in C++, you receive it in your app and if you want your app to close, you can then instruct it to do so. What I wanted to say is once the callback is received in C++, one should use JUCEApplicationBase::quit() rather than trying to call Activity.finishAndRemoveTask() directly (using JNI). On a side note, finishAndRemoveTask is only available on Android 5.0 and above so to run on older devices, you would have to check OS version and call finish instead for older devices. Rather than having to handle all this yourself, you can just call JUCEApplicationBase::quit() and let JUCE do it for you :slight_smile:

Cheers,
Lukasz

From the Android Developers reference:

onBackPressed

added in API level 5
void onBackPressed ()

Called when the activity has detected the user’s press of the back key. The default implementation simply finishes the current activity, but you can override this to do whatever you want.

My understanding is that the default onBackPressed method of the activity itself calls the activity finish() method, that in turns triggers the closing of the Juce lib. Isn’t that so?

Yes, the default behaviour of back button press would result in calling Activity.finish, but that would not unload JUCE lib and that would not close the app completely.

Rather than using a default behaviour in Java, we give people opportunity to decide what they want to do, by calling JUCEApplicationBase::backButtonPressed() in C++. If you then called JUCEApplicationBase::quit() from JUCEApplicationBase::backButtonPressed(), it would result in calling Activity.finish, which would be exactly the same behaviour as Java’s default. This is not a hack, just an extra step to allow you to do something else if you wish, but you could just say “Yes, I want to quit the app”.

Then, the problem with that was that calling Activity.finish does not really close the app, and Juce lib does not get unloaded. When a user comes back to the app from recent apps list, assertions will be hit in JUCE because we try to initialise the app again, while the previous instance app has not been fully closed. This is why I added code to fully exit the app. So now if you call JUCEApplicationBase::quit(), it will really quit the app.

To summarise, call JUCEApplicationBase::quit() in your JUCEApplicationBase::backButtonPressed() if you want to close the app. That will instruct Java to finish the activity, but it will also fully close the app.

Ah Ok, I understand now. Was just assuming that there was some callback in the native (Juce) part that was automatically triggering the shutdown of the native lib when the Java activity was finishing.

I’ll make sure to call the quit() method explicitly.

Thanks for all this precious information.

Mariano

Just one final question on this. Considering I’m working on commercial products and therefore prefer to use the master branch, can I expect this fix to be applied there soon, or should I apply them myself temporarily in my master version of the code?

I’m still a bit confused about the version management of the lib. Working with develop has the advantage to include all fixes, but maybe some new bugs, while master is more stable but doesn’t have some fixes… How do you guys reconcile that? I’d be happy to know.

Thanks.

Mariano

In general it’s safest to work with the master branch. It all depends on your situation, if you can afford working with develop and you want an early access to some new features, develop might be better for you. Usually we update master branch when releasing a new version, but we also do occasional updates & bug fixes on that branch if they are serious enough. In this particular case, the change is not simple to put into master branch unfortunately and I would recommend to apply the fix to your local master branch version. You will need to re-save your project in Projucer for the changes to take effect.

Awesome, thanks. You guys rock!