Fix #7536: Android build fails to start (#8554)

This was a regression from #7435 which introduced threads and caused JNI
to misbehave and fail to load our expected classes. Provide a workaround
based on the description in https://stackoverflow.com/a/16302771 which
stores a main thread's class loader and uses that in neighbouring
threads.
This commit is contained in:
Michał Janiszewski 2019-01-04 19:48:26 +01:00 committed by Michael Steenbeek
parent 20496b0390
commit 7e769ed662
6 changed files with 83 additions and 1 deletions

View File

@ -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.

View File

@ -13,8 +13,10 @@
# include <SDL.h>
# include <dlfcn.h>
# include <jni.h>
# include <openrct2/common.h>
# include <openrct2/core/String.hpp>
# include <openrct2/platform/platform.h>
# include <openrct2/ui/UiContext.h>
# include <sstream>
# include <stdexcept>

View File

@ -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, "<init>", "(Ljava/lang/String;)V");
jstring jniPath = env->NewStringUTF(path.data());

View File

@ -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<AndroidClassLoader> acl;
void platform_android_init_class_loader()
{
acl = std::make_shared<AndroidClassLoader>();
}
jclass platform_android_find_class(JNIEnv* env, const char* name)
{
return static_cast<jclass>(
env->CallObjectMethod(AndroidClassLoader::_classLoader, AndroidClassLoader::_findClassMethod, env->NewStringUTF(name)));
}
#endif

View File

@ -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();

View File

@ -15,6 +15,10 @@
#include <string>
#include <time.h>
#ifdef __ANDROID__
# include <jni.h>
#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