Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change Activity with JNI Call or using Openfeint causes App-Crash

I have a huge problem when I want to change the Activity of my Android-Application with a JNI call from my C++ Code. The App uses cocos2d-x for rendering. The concrete situation is that I want to open the OpenFeint-Dashboard in Java using this very small function:

void launchOpenFeintDashboard() {
    Dashboard.open();
}

This function is then called from C++ with a simple JNI-Call:

void
OFWrapper::launchDashboard() {
// init openfeint
CCLog("CPP Init OpenFeint Dashboard");

CCDirector::sharedDirector()->pause();

jmethodID javamethod = JNIManager::env()->GetMethodID(JNIManager::mainActivity(), "launchOpenFeintDashboard", "()V");
if (javamethod == 0)
    return;
JNIManager::env()->CallVoidMethod( JNIManager::mainActivityObj(), javamethod );

CCLog("CPP Init OpenFeint Dashboard done");
}

The JNIManager Class implementation is also very simple and basic:

#include "JNIManager.h"
#include <cstdlib>

static JNIEnv* sJavaEnvironment = NULL;
static jobject sMainActivityObject = NULL;
static jclass  sMainActivity = NULL;


extern "C" {
    JNIEXPORT void JNICALL Java_net_plazz_mainzelapp_mainzelapp_sendJavaEnvironment(JNIEnv* env, jobject obj);
};

// this function is called from JAVA at startup to get the env
JNIEXPORT void JNICALL Java_net_plazz_mainzelapp_mainzelapp_sendJavaEnvironment(JNIEnv* env, jobject obj)
{
sJavaEnvironment = env;
sMainActivityObject = obj;
sMainActivity = JNIManager::env()->GetObjectClass(obj);
}



JNIEnv* 
JNIManager::env() 
{
return sJavaEnvironment;
}

jobject 
JNIManager::mainActivityObj() 
{
return sMainActivityObject;
}

jclass 
JNIManager::mainActivity() 
{
return sMainActivity;
}

From my point of view, cocos2d-x has some cirical problems when changing the activity with a JNI call, because I also get an App-Crash when changing the Activity to any own Activity.

BUT, also when I simply use OpenFeint to update an Achievement with a JNI call I get an App-Crash, similar as when changing the Activity:

void updateAchievementProgress( final String achievementIdStr, final String progressStr ) {
    Log.v("CALLBACK", "updateAchievementProgress (tid:" + Thread.currentThread().getId() + ")");

    float x = Float.valueOf(progressStr).floatValue();
    final Achievement a = new Achievement(achievementIdStr);
    a.updateProgression(x, new Achievement.UpdateProgressionCB() {
        @Override
        public void onSuccess(boolean b) {
            Log.e("In Achievement", "UpdateProgression");
            a.notifyAll();
        }

        @Override
        public void onFailure(String exceptionMessage) {
            Log.e("In Achievement", "Unlock failed");
            a.notifyAll();
        }
    });
    Log.v("CALLBACK", "updateAchievementProgress done (tid:" + Thread.currentThread().getId() + ")");
}

This brings me to a point on what I would say, that Android or Cocos2d-x has some problem when doing something asyncronously (update Achievement) or when changing the Activity in combination with using the NDK (I use NDKr7, but same on NDKr5).

You should also know that I already have some other functions defined in Java which are called with a JNI call and which work properly!

Maybe I've done something wrong, can somebody give me some advide on this or a working sample code of how to change the activity. Maybe it's a problem with Cocos2d-x.

Thanks.

like image 958
Andy Reimann Avatar asked Mar 08 '12 10:03

Andy Reimann


People also ask

What JNI calls?

In software design, the Java Native Interface (JNI) is a foreign function interface programming framework that enables Java code running in a Java virtual machine (JVM) to call and be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages ...

What is the use of JNI in Android?

JNI is the Java Native Interface. It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++).

What is NDK and JNI?

JNI is just the way that Java handles calling into native/C++ code, and calling back into Java from there. It has nothing to say about Android - it is a Java language feature. The Android NDK is a way to write Android applications using code called by JNI.

Does JNI create new process?

The advantage of using JNI is that both the calling program and the called program run in the same process (job) while the other methods start a new process (job). This makes JNI calls faster at startup time and less resource-intensive.


2 Answers

I don't know about Dalvik implementation, but @Goz is right: the scope for JNIEnv pointer is just for the duration of the JNI function you call. If you want to do callback from native code to Java, you cannot just save JNIEnv from the previous call, because that one might no longer be valid (hence appcrashes). If you would have only one thread doing everyhting, then it would work.

What you need to do (if you have multiple threads), is to obtain a valid JNIEnv pointer each time you are going to callback. In the initialisation function, you save a pointer to the current running virtual machine:

JavaVM *jvm;
env->GetJavaVM(&jvm);

You can then use this reference to a running virtual machine to obtain a valid JNIEnv pointer by calling:

JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);

Then you can work with the env and when you are finished, don't forget to call

jvm->DetachCurrentThread();

The attach/detach will cause Java to register the caller (which can be a native thread) with a Thread object, which allows it to be treated as a Java thread.

DISCLAIMER: At least, this is how you do it in desktop Java. Don't know about Dalvik implementation, but from the look of it, they just copied the technology.

like image 140
Jakub Zaverka Avatar answered Sep 30 '22 07:09

Jakub Zaverka


I have found the answer for my case. It was was easy to fix but complex to find. When the app changes to a new activity, the nativeOnPause method from cocos2d-x MessageJNI is called. This method is supposed to call a CCApplication::sharedApplication(), but one of my classes had previously called the CCApplication destructor, which cleared the shared singleton to null.

EDIT: This is a complete edit from the original post, so comments don't make sense anymore.

like image 32
MLProgrammer-CiM Avatar answered Sep 30 '22 07:09

MLProgrammer-CiM