Skip to content

Commit

Permalink
Android life cycle behavior more closely matches iOS
Browse files Browse the repository at this point in the history
This change also decouples the pause/resume handling from the video subsystem on Android, so applications that don't use SDL for video can get application life cycle events.

The semantics for the life cycle events are that they need to be handled in an event watch callback, and once they've been delivered, the application will block until it's been resumed. SDL_HINT_ANDROID_BLOCK_ON_PAUSE can be used to control that behavior, and if that's set to "0", then the application will continue to run in the background at low CPU usage until being resumed or stopped.

SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO has been removed, and the audio will be paused when the application is paused.

Fixes libsdl-org#3193
  • Loading branch information
slouken committed Jul 24, 2024
1 parent fff783d commit ca4bd4b
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 205 deletions.
101 changes: 73 additions & 28 deletions docs/README-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,42 +189,87 @@ disable this behaviour, see for example:
http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/


Pause / Resume behaviour
Activity lifecycle
================================================================================

On Android the application goes through a fixed life cycle and you will get
notifications of state changes via application events. When these events
are delivered you must handle them in an event callback because the OS may
not give you any processing time after the events are delivered.

e.g.

int HandleAppEvents(void *userdata, SDL_Event *event)
{
switch (event->type)
{
case SDL_EVENT_TERMINATING:
/* Terminate the app.
Shut everything down before returning from this function.
*/
return 0;
case SDL_EVENT_LOW_MEMORY:
/* You will get this when your app is paused and iOS wants more memory.
Release as much memory as possible.
*/
return 0;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
/* Prepare your app to go into the background. Stop loops, etc.
This gets called when the user hits the home button, or gets a call.

You should not make any OpenGL graphics calls or use the rendering API,
in addition, you should set the render target to NULL, if you're using
it, e.g. call SDL_SetRenderTarget(renderer, NULL).
*/
return 0;
case SDL_EVENT_DID_ENTER_BACKGROUND:
/* Your app is NOT active at this point. */
return 0;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
/* This call happens when your app is coming back to the foreground.
Restore all your state here.
*/
return 0;
case SDL_EVENT_DID_ENTER_FOREGROUND:
/* Restart your loops here.
Your app is interactive and getting CPU again.

You have access to the OpenGL context or rendering API at this point.
However, there's a chance (on older hardware, or on systems under heavy load),
where the graphics context can not be restored. You should listen for the
event SDL_EVENT_RENDER_DEVICE_RESET and recreate your OpenGL context and
restore your textures when you get it, or quit the app.
*/
return 0;
default:
/* No special processing, add it to the event queue */
return 1;
}
}

int main(int argc, char *argv[])
{
SDL_SetEventFilter(HandleAppEvents, NULL);

... run your main loop

return 0;
}


Note that if you are using main callbacks instead of a standard C main() function,
your SDL_AppEvent() callback will run as these events arrive and you do not need to
use SDL_SetEventFilter.

If SDL_HINT_ANDROID_BLOCK_ON_PAUSE hint is set (the default),
the event loop will block itself when the app is paused (ie, when the user
returns to the main Android dashboard). Blocking is better in terms of battery
use, and it allows your app to spring back to life instantaneously after resume
(versus polling for a resume message).

Upon resume, SDL will attempt to restore the GL context automatically.
In modern devices (Android 3.0 and up) this will most likely succeed and your
app can continue to operate as it was.

However, there's a chance (on older hardware, or on systems under heavy load),
where the GL context can not be restored. In that case you have to listen for
a specific message (SDL_EVENT_RENDER_DEVICE_RESET) and restore your textures
manually or quit the app.

You should not use the SDL renderer API while the app going in background:
- SDL_EVENT_WILL_ENTER_BACKGROUND:
after you read this message, GL context gets backed-up and you should not
use the SDL renderer API.

When this event is received, you have to set the render target to NULL, if you're using it.
(eg call SDL_SetRenderTarget(renderer, NULL))

- SDL_EVENT_DID_ENTER_FOREGROUND:
GL context is restored, and the SDL renderer API is available (unless you
receive SDL_EVENT_RENDER_DEVICE_RESET).

Activity lifecycle
================================================================================

You can control activity re-creation (eg. onCreate()) behaviour. This allows to keep
or re-initialize java and native static datas, see SDL_hints.h:
- SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY
You can control activity re-creation (eg. onCreate()) behaviour. This allows you
to choose whether to keep or re-initialize java and native static datas, see
SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY in SDL_hints.h.

Mouse / Touch events
================================================================================
Expand Down
1 change: 1 addition & 0 deletions docs/README-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,7 @@ Calling SDL_GetHint() with the name of the hint being changed from within a hint

The following hints have been removed:
* SDL_HINT_ACCELEROMETER_AS_JOYSTICK
* SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO - the audio will be paused when the application is paused, and SDL_HINT_ANDROID_BLOCK_ON_PAUSE can be used to control that
* SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS - gamepad buttons are always positional
* SDL_HINT_GRAB_KEYBOARD - use SDL_SetWindowKeyboardGrab() instead
* SDL_HINT_IDLE_TIMER_DISABLED - use SDL_DisableScreenSaver() instead
Expand Down
8 changes: 4 additions & 4 deletions include/SDL3/SDL_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ typedef enum SDL_EventType
/* Application events */
SDL_EVENT_QUIT = 0x100, /**< User-requested quit */

/* These application events have special meaning on iOS, see README-ios.md for details */
SDL_EVENT_TERMINATING, /**< The application is being terminated by the OS
/* These application events have special meaning on iOS and Android, see README-ios.md and README-android.md for details */
SDL_EVENT_TERMINATING, /**< The application is being terminated by the OS
Called on iOS in applicationWillTerminate()
Called on Android in onDestroy()
*/
SDL_EVENT_LOW_MEMORY, /**< The application is low on memory, free memory if possible.
SDL_EVENT_LOW_MEMORY, /**< The application is low on memory, free memory if possible.
Called on iOS in applicationDidReceiveMemoryWarning()
Called on Android in onLowMemory()
Called on Android in onTrimMemory()
*/
SDL_EVENT_WILL_ENTER_BACKGROUND, /**< The application is about to enter the background
Called on iOS in applicationWillResignActive()
Expand Down
15 changes: 0 additions & 15 deletions include/SDL3/SDL_hints.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,6 @@ extern "C" {
*/
#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE "SDL_ANDROID_BLOCK_ON_PAUSE"

/**
* A variable to control whether SDL will pause audio in background.
*
* The variable can be set to the following values:
*
* - "0": Not paused, requires that SDL_HINT_ANDROID_BLOCK_ON_PAUSE be set to
* "0"
* - "1": Paused. (default)
*
* This hint should be set before SDL is initialized.
*
* \since This hint is available since SDL 3.0.0.
*/
#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO "SDL_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO"

/**
* A variable to control whether we trap the Android back button to handle it
* manually.
Expand Down
9 changes: 9 additions & 0 deletions src/SDL.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "thread/SDL_thread_c.h"
#include "video/SDL_pixels_c.h"
#include "video/SDL_video_c.h"
#include "video/android/SDL_androidevents.h"
#include "filesystem/SDL_filesystem_c.h"

#define SDL_INIT_EVERYTHING ~0U
Expand Down Expand Up @@ -225,6 +226,10 @@ int SDL_InitSubSystem(Uint32 flags)

SDL_InitMainThread();

#ifdef SDL_PLATFORM_ANDROID
Android_InitEvents();
#endif

#ifdef SDL_USE_LIBDBUS
SDL_DBus_Init();
#endif
Expand Down Expand Up @@ -571,6 +576,10 @@ void SDL_Quit(void)
SDL_DBus_Quit();
#endif

#ifdef SDL_PLATFORM_ANDROID
Android_QuitEvents();
#endif

SDL_SetObjectsInvalid();
SDL_ClearHints();
SDL_AssertionsQuit();
Expand Down
10 changes: 7 additions & 3 deletions src/core/android/SDL_android.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ static jobject javaAssetManagerRef = 0;
/* Re-create activity hint */
static SDL_AtomicInt bAllowRecreateActivity;

static SDL_Mutex *Android_ActivityMutex = NULL;
SDL_Semaphore *Android_PauseSem = NULL;
SDL_Semaphore *Android_ResumeSem = NULL;

/*******************************************************************************
Functions called by JNI
*******************************************************************************/
Expand Down Expand Up @@ -903,18 +907,18 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
}

/* Lock / Unlock Mutex */
void Android_ActivityMutex_Lock(void)
void Android_LockActivityMutex(void)
{
SDL_LockMutex(Android_ActivityMutex);
}

void Android_ActivityMutex_Unlock(void)
void Android_UnlockActivityMutex(void)
{
SDL_UnlockMutex(Android_ActivityMutex);
}

/* Lock the Mutex when the Activity is in its 'Running' state */
void Android_ActivityMutex_Lock_Running(void)
void Android_LockActivityMutexOnceRunning(void)
{
int pauseSignaled = 0;
int resumeSignaled = 0;
Expand Down
10 changes: 7 additions & 3 deletions src/core/android/SDL_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,19 @@ SDL_bool SDL_IsAndroidTV(void);
SDL_bool SDL_IsChromebook(void);
SDL_bool SDL_IsDeXMode(void);

void Android_ActivityMutex_Lock(void);
void Android_ActivityMutex_Unlock(void);
void Android_ActivityMutex_Lock_Running(void);
void Android_LockActivityMutex(void);
void Android_UnlockActivityMutex(void);
void Android_LockActivityMutexOnceRunning(void);

/* File Dialogs */
SDL_bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata,
const SDL_DialogFileFilter *filters, int nfilters, SDL_bool forwrite,
SDL_bool multiple);

/* Semaphores for event state processing */
extern SDL_Semaphore *Android_PauseSem;
extern SDL_Semaphore *Android_ResumeSem;

/* Ends C function definitions when using C++ */
#ifdef __cplusplus
/* *INDENT-OFF* */
Expand Down
9 changes: 7 additions & 2 deletions src/events/SDL_events.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "../sensor/SDL_sensor_c.h"
#endif
#include "../video/SDL_sysvideo.h"
#include "../video/android/SDL_androidevents.h"

/* An arbitrary limit so we don't have unbounded growth */
#define SDL_MAX_QUEUED_EVENTS 65535
Expand Down Expand Up @@ -1173,18 +1174,22 @@ void SDL_FlushEvents(Uint32 minType, Uint32 maxType)
/* Run the system dependent event loops */
static void SDL_PumpEventsInternal(SDL_bool push_sentinel)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();

/* Free old event memory */
SDL_FreeTemporaryMemory();

/* Release any keys held down from last frame */
SDL_ReleaseAutoReleaseKeys();

#ifdef SDL_PLATFORM_ANDROID
/* Android event processing is independent of the video subsystem */
Android_PumpEvents();
#else
/* Get events from the video subsystem */
SDL_VideoDevice *_this = SDL_GetVideoDevice();
if (_this) {
_this->PumpEvents(_this);
}
#endif

#ifndef SDL_AUDIO_DISABLED
SDL_UpdateAudio();
Expand Down
6 changes: 3 additions & 3 deletions src/render/SDL_render.c
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, SDL_TRUE);

#ifdef SDL_PLATFORM_ANDROID
Android_ActivityMutex_Lock_Running();
Android_LockActivityMutexOnceRunning();
#endif

if ((!window && !surface) || (window && surface)) {
Expand Down Expand Up @@ -1127,7 +1127,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
SDL_renderers = renderer;

#ifdef SDL_PLATFORM_ANDROID
Android_ActivityMutex_Unlock();
Android_UnlockActivityMutex();
#endif

SDL_ClearError();
Expand All @@ -1139,7 +1139,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, SDL_FALSE);

#ifdef SDL_PLATFORM_ANDROID
Android_ActivityMutex_Unlock();
Android_UnlockActivityMutex();
#endif
SDL_free(renderer->texture_formats);
SDL_free(renderer);
Expand Down
Loading

0 comments on commit ca4bd4b

Please sign in to comment.