From dfd80f3d762441e34e501c18a20b50c09fe3730f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 5 Apr 2023 21:54:36 +0200 Subject: [PATCH] Android: control activity re-creation --- WhatsNew.txt | 1 + .../main/java/org/libsdl/app/SDLActivity.java | 22 ++++++++++++++ docs/README-android.md | 7 +++++ include/SDL3/SDL_hints.h | 13 +++++++++ src/core/android/SDL_android.c | 29 ++++++++++++++++++- 5 files changed, 71 insertions(+), 1 deletion(-) diff --git a/WhatsNew.txt b/WhatsNew.txt index ea88419aa..1e91ba8cb 100644 --- a/WhatsNew.txt +++ b/WhatsNew.txt @@ -29,3 +29,4 @@ General: * Added SDL_GetRenderVSync() to get vsync of the given renderer * Added SDL_PlayAudioDevice() to start audio playback * Added SDL_ConvertAudioSamples() to convert audio samples from one format to another +* Added SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY hint to control re-creation of Android SDL activity. diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index de03ba790..db524b639 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -221,6 +221,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; + protected static boolean mSDLMainFinished = false; + protected static boolean mActivityCreated = false; protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { @@ -324,6 +326,24 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); + + /* Control activity re-creation */ + if (mSDLMainFinished || mActivityCreated) { + boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity(); + if (mSDLMainFinished) { + Log.v(TAG, "SDL main() finished"); + } + if (allow_recreate) { + Log.v(TAG, "activity re-created"); + } else { + Log.v(TAG, "activity finished"); + System.exit(0); + return; + } + } + + mActivityCreated = true; + try { Thread.currentThread().setName("SDLActivity"); } catch (Exception e) { @@ -950,6 +970,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static native void nativePermissionResult(int requestCode, boolean result); public static native void onNativeLocaleChanged(); public static native void onNativeDarkModeChanged(boolean enabled); + public static native boolean nativeAllowRecreateActivity(); /** * This method is called by SDL using JNI. @@ -1902,6 +1923,7 @@ class SDLMain implements Runnable { if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) { // Let's finish the Activity SDLActivity.mSDLThread = null; + SDLActivity.mSDLMainFinished = true; SDLActivity.mSingleton.finish(); } // else: Activity is already being destroyed diff --git a/docs/README-android.md b/docs/README-android.md index 4f6b75a0a..0c21ce8f1 100644 --- a/docs/README-android.md +++ b/docs/README-android.md @@ -218,6 +218,13 @@ You should not use the SDL renderer API while the app going in background: GL context is restored, and the SDL renderer API is available (unless you receive SDL_EVENT_RENDER_DEVICE_RESET). +Activity lifecyle +================================================================================ + +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 + Mouse / Touch events ================================================================================ diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 70f95957b..09830b563 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -145,6 +145,19 @@ extern "C" { */ #define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON" +/** + * \brief A variable to control whether SDL activity is allowed to be re-created. + * If so, java static datas and static datas from native libraries remain with their current values. + * When not allowed, the activity terminates with exit(0) to be fully re-initialized afterward. + * + * The variable can be set to the following values: + * "0" - Not allowed. (default) + * "1" - Allowed. + * + * The value of this hint is used at runtime, so it can be changed at any time. + */ +#define SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY" + /** * \brief Specify an application name. * diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 028bd1fba..6a6cc8305 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -33,6 +33,7 @@ #include "../../joystick/android/SDL_sysjoystick_c.h" #include "../../haptic/android/SDL_syshaptic_c.h" #include "../../hidapi/android/hid.h" +#include "../../SDL_hints_c.h" #include #include @@ -166,6 +167,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)( JNIEnv *env, jclass cls, jint requestCode, jboolean result); +JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( + JNIEnv *env, jclass jcls); + static JNINativeMethod SDLActivity_tab[] = { { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) }, { "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) }, @@ -197,7 +201,8 @@ static JNINativeMethod SDLActivity_tab[] = { { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) }, { "onNativeOrientationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeOrientationChanged) }, { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) }, - { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) } + { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) }, + { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) }, }; /* Java class SDLInputConnection */ @@ -375,6 +380,9 @@ static void Internal_Android_Destroy_AssetManager(void); static AAssetManager *asset_manager = NULL; static jobject javaAssetManagerRef = 0; +/* Re-create activity hint */ +static SDL_AtomicInt bAllowRecreateActivity; + /******************************************************************************* Functions called by JNI *******************************************************************************/ @@ -525,6 +533,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab)); register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab)); register_methods(env, "org/libsdl/app/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab)); + SDL_AtomicSet(&bAllowRecreateActivity, SDL_FALSE); return JNI_VERSION_1_4; } @@ -731,6 +740,21 @@ typedef int (*SDL_main_func)(int argc, char *argv[]); static int run_count = 1; +static void SDLCALL SDL_AllowRecreateActivityChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + if (SDL_GetStringBoolean(hint, SDL_FALSE)) { + SDL_AtomicSet(&bAllowRecreateActivity, SDL_TRUE); + } else { + SDL_AtomicSet(&bAllowRecreateActivity, SDL_FALSE); + } +} + +JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( + JNIEnv *env, jclass jcls) +{ + return SDL_AtomicGet(&bAllowRecreateActivity); +} + /* Start up the SDL app */ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, jstring library, jstring function, jobject array) { @@ -739,6 +763,9 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, void *library_handle; __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain() %d time", run_count); + if (run_count == 1) { + SDL_AddHintCallback(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, SDL_AllowRecreateActivityChanged, NULL); + } run_count += 1; /* Save JNIEnv of SDLThread */