diff --git a/distribution/changelog.txt b/distribution/changelog.txt index efbb5fecc2..2eda002e26 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -21,6 +21,7 @@ - Fix: [#6191] OpenRCT2 fails to run when the path has an emoji in it. - Fix: [#7439] Placement messages have mixed strings - Fix: [#7473] Disabling sound effects also disables "Disable audio on focus loss". +- Fix: [#7536] Android builds fail to start. - Fix: [#7689] Deleting 0-tile maze gives a MONEY32_UNDEFINED (negative) refund. - Fix: [#7828] Copied entrances and exits stay when demolishing ride. - Fix: [#7945] Client IP address is logged as `(null)` in server logs. diff --git a/src/openrct2-ui/UiContext.Android.cpp b/src/openrct2-ui/UiContext.Android.cpp index fcb483d25d..a5152e090f 100644 --- a/src/openrct2-ui/UiContext.Android.cpp +++ b/src/openrct2-ui/UiContext.Android.cpp @@ -13,8 +13,10 @@ # include # include +# include # include # include +# include # include # include # include diff --git a/src/openrct2/core/ZipAndroid.cpp b/src/openrct2/core/ZipAndroid.cpp index c3bf694ca0..07975a29a8 100644 --- a/src/openrct2/core/ZipAndroid.cpp +++ b/src/openrct2/core/ZipAndroid.cpp @@ -9,6 +9,7 @@ #ifdef __ANDROID__ +# include "../platform/platform.h" # include "IStream.hpp" # include "Zip.h" @@ -26,7 +27,7 @@ public: // retrieve the JNI environment. JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); - jclass jniClass = env->FindClass("website/openrct2/ZipArchive"); + jclass jniClass = platform_android_find_class(env, "website/openrct2/ZipArchive"); jmethodID constructor = env->GetMethodID(jniClass, "", "(Ljava/lang/String;)V"); jstring jniPath = env->NewStringUTF(path.data()); diff --git a/src/openrct2/platform/Android.cpp b/src/openrct2/platform/Android.cpp index c396fea99a..f9d2c50eee 100644 --- a/src/openrct2/platform/Android.cpp +++ b/src/openrct2/platform/Android.cpp @@ -62,4 +62,60 @@ bool platform_get_steam_path(utf8* outPath, size_t outSize) return false; } +AndroidClassLoader::AndroidClassLoader() +{ + log_info("Obtaining JNI class loader"); + + // This is a workaround to be able to call JNI's ClassLoader from non-main + // thread, based on https://stackoverflow.com/a/16302771 + + // Apparently it's OK to use it from across different thread, but JNI + // only looks for ClassLoader in the _current_ thread and fails to find + // it when searched for from a native library's non-main thread. + + // The solution below works by obtaining a ClassLoader reference in main + // thread and caching it for future use from any thread, instead of using + // it via env->FindClass(). ClassLoader itself is abstract, so we cannot + // create it directly; instead we take an arbitrary class and call + // getClassLoader() on it to create a reference that way. + + // If we're here, SDL's JNI_OnLoad has already been called and set env + JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); + + // Take an arbitrary class. While the class does not really matter, it + // makes sense to use one that's most likely already loaded and is unlikely + // to be removed from code. + auto randomClass = env->FindClass("website/openrct2/MainActivity"); + jclass classClass = env->GetObjectClass(randomClass); + + // Get its class loader + auto classLoaderClass = env->FindClass("java/lang/ClassLoader"); + auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); + + // Store the class loader and its findClass method for future use + _classLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod)); + _findClassMethod = env->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); +} + +AndroidClassLoader::~AndroidClassLoader() +{ + JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); + env->DeleteGlobalRef(_classLoader); +} + +jobject AndroidClassLoader::_classLoader; +jmethodID AndroidClassLoader::_findClassMethod; + +static std::shared_ptr acl; + +void platform_android_init_class_loader() +{ + acl = std::make_shared(); +} + +jclass platform_android_find_class(JNIEnv* env, const char* name) +{ + return static_cast( + env->CallObjectMethod(AndroidClassLoader::_classLoader, AndroidClassLoader::_findClassMethod, env->NewStringUTF(name))); +} #endif diff --git a/src/openrct2/platform/Shared.cpp b/src/openrct2/platform/Shared.cpp index b22e90a7bb..1955bdba5f 100644 --- a/src/openrct2/platform/Shared.cpp +++ b/src/openrct2/platform/Shared.cpp @@ -212,6 +212,10 @@ void core_init() { initialised = true; +#ifdef __ANDROID__ + platform_android_init_class_loader(); +#endif // __ANDROID__ + platform_ticks_init(); bitcount_init(); mask_init(); diff --git a/src/openrct2/platform/platform.h b/src/openrct2/platform/platform.h index b8da94c69e..6726dcb734 100644 --- a/src/openrct2/platform/platform.h +++ b/src/openrct2/platform/platform.h @@ -15,6 +15,10 @@ #include #include +#ifdef __ANDROID__ +# include +#endif // __ANDROID__ + struct TTFFontDescriptor; struct rct2_install_info; @@ -160,4 +164,18 @@ void macos_disallow_automatic_window_tabbing(); utf8* macos_str_decomp_to_precomp(utf8* input); #endif +#ifdef __ANDROID__ +class AndroidClassLoader +{ +public: + AndroidClassLoader(); + ~AndroidClassLoader(); + static jobject _classLoader; + static jmethodID _findClassMethod; +}; + +void platform_android_init_class_loader(); +jclass platform_android_find_class(JNIEnv* env, const char* name); +#endif // __ANDROID__ + #endif