Okay the solution is relatively simple and is caused by an old JNI code.
The problem is, in older versions of Android, the jni let the native code handle the pointers to jvm objects (jobjects) directly so you could store a reference just writing something like this:
ActivityClass = jenv->GetObjectClass(MainActivity);
Which is fine as long as the GC doesn't change the location of variables BUT since ICS this feature was incorporated (but for some reason they still let this code works fine until Lollipop). So now, instead of direct pointers you have some indirect references and the previous code it's no longer safe.
(for a much better explanation, you can read this article)
The most straightforward solution is to locate the assignment of the global references in the native code and add a
NewGlobalRef(...)
call before it.
The first part is in the Platform_Android header
// Platform_Android.h
extern "C" JNIEXPORT void JNICALL Java_##Package##_##Activity##_NativeCacheObject(JNIEnv * env, jobject obj) \
{ \
SF_DEBUG_MESSAGE(1, #Activity " CacheObject START"); \
jobject globRefObj = env->NewGlobalRef(obj); \
pImpl->JObj = globRefObj; \
pImpl->OnNativeCacheObject( env ); \
SF_DEBUG_MESSAGE(1, #Activity " CacheObject END"); \
} \
\
extern "C" JNIEXPORT void JNICALL Java_##Package##_##Activity##_NativeClearObject(JNIEnv * env, jobject obj) \
{ \
SF_DEBUG_MESSAGE(1, #Activity " ClearObject START"); \
if((pImpl->JObj)) \
env->DeleteGlobalRef(pImpl->JObj); \
pImpl->JObj = NULL; \
pImpl->OnNativeClearObject( env ); \
SF_DEBUG_MESSAGE(1, #Activity " ClearObject END"); \
} \
\
extern "C" JNIEXPORT void JNICALL Java_##Package##_##Activity##_NativeOnCreate(JNIEnv * env, jobject obj) \
{ \
SF_DEBUG_MESSAGE(1, #Activity " NativeOnCreate START"); \
pImpl->MainActivity = obj; \
pImpl->SetupFiles(); \
SF_DEBUG_MESSAGE(1, #Activity " NativeOnCreate END"); \
}
Notice that I left
pImpl->MainActivity = obj;
as it on purpose, because I chose to keep the
pImpl->jObj
(you'll see then why) instead (yes, they point the same object) for future uses apart from the inmediate SetupFiles, OnNativeCacheObject and OnNativeClearObject.
Now in the Platform_Android_GL.cpp
//Platform_Android_GL.cpp
void AppImpl::SetupFiles()
{
if (!SetupFilesComplete)
{
// Only proceed if we have a valid JNI environment
// (NativeAppInit has completed)
JNIEnv* jenv;
if (!pJVM || pJVM->GetEnv((void**)&jenv, JNI_VERSION_1_6) < 0)
{
SF_DEBUG_ERROR(1, "SetupFiles: Cannot get jni env");
return;
}
// We are still in the jni call scope so we can use MainActivity
ActivityClass = jenv->GetObjectClass(MainActivity);
// set the global ref
ActivityClass = (jclass)jenv->NewGlobalRef(ActivityClass);
jmethodID getAssets = jenv->GetMethodID(ActivityClass, "getAssets", "()Landroid/content/res/AssetManager;");
AssetManager = jenv->CallObjectMethod(MainActivity, getAssets);
// set the global ref
AssetManager = jenv->NewGlobalRef(AssetManager);
AssetManagerClass = jenv->GetObjectClass(AssetManager);
// set the global ref
AssetManagerClass = (jclass)jenv->NewGlobalRef(AssetManagerClass);
jmethodID getFilesDir = jenv->GetMethodID(ActivityClass, "getFilesDir", "()Ljava/io/File;");
jobject filesDirH = jenv->CallObjectMethod(MainActivity, getFilesDir);
jclass FileClass = jenv->GetObjectClass(filesDirH);
jmethodID getAbsPath = jenv->GetMethodID(FileClass, "getAbsolutePath", "()Ljava/lang/String;");
jstring filesDir = (jstring) jenv->CallObjectMethod(filesDirH, getAbsPath);
jboolean copy;
const char* fs = jenv->GetStringUTFChars(filesDir, ©);
// Does not exist by default.
mkdir(fs, 0700);
FilesDir = fs;
FilesDir += "/";
SF_DEBUG_MESSAGE1(1, "Files dir: %s", fs);
jenv->ReleaseStringUTFChars(filesDir, fs);
jmethodID getCacheDir = jenv->GetMethodID(ActivityClass, "getCacheDir", "()Ljava/io/File;");
jobject cacheDirH = jenv->CallObjectMethod(MainActivity, getCacheDir);
jstring cacheDir = (jstring) jenv->CallObjectMethod(cacheDirH, getAbsPath);
const char* fcs = jenv->GetStringUTFChars(cacheDir, ©);
// Does not exist by default.
mkdir(fcs, 0755);
CacheDir = fcs;
CacheDir += "/";
SF_DEBUG_MESSAGE1(1, "Cache dir: %s", fcs);
jenv->ReleaseStringUTFChars(cacheDir, fcs);
SetupFilesComplete = true;
}
}
void AppImpl::OnNativeCacheObject( JNIEnv * jenv )
{
SF_DEBUG_MESSAGE(1, "OnNativeCacheObject");
// This call is made from onStart, so there are two cases in which this call is made
// 1) When running the application for the first time or after destroying it (onDestroy call). In that case we have already cached the fields
// Or 2) When we resume the application (onRestart). In that case we have to cache the objects
if ( !ActivityClass )
{
ActivityClass = jenv->GetObjectClass(JObj);
// set the global ref
ActivityClass = (jclass)jenv->NewGlobalRef(ActivityClass);
}
if ( !AssetManager )
{
jmethodID getAssets = jenv->GetMethodID(ActivityClass, "getAssets", "()Landroid/content/res/AssetManager;");
AssetManager = jenv->CallObjectMethod(JObj, getAssets);
// set the global ref
AssetManager = jenv->NewGlobalRef(AssetManager);
}
if ( !AssetManagerClass )
{
AssetManagerClass = jenv->GetObjectClass(AssetManager);
// set the global ref
AssetManagerClass = (jclass)jenv->NewGlobalRef(AssetManagerClass);
}
}
void AppImpl::OnNativeClearObject( JNIEnv *jenv )
{
SF_DEBUG_MESSAGE(1, "OnNativeClearObject");
if( ActivityClass )
{
jenv->DeleteGlobalRef( ActivityClass );
ActivityClass = 0;
}
if ( AssetManager )
{
jenv->DeleteGlobalRef( AssetManager );
AssetManager = 0;
}
if( AssetManagerClass )
{
jenv->DeleteGlobalRef( AssetManagerClass );
AssetManagerClass = 0;
}
}
This is the simplest solution, you can tidy it up a little bit by eliminating the variable duplication (for example) or even writing some jobject wrapper, but I guess that's the bare minimum changes you need to make.*
Hope it's clear!
PS: Perhaps I forgot some other parts in which changes are necessary (I can't tell for sure) but I think you get the idea.