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

2

5

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.

Hodgkins answered 8/3, 2012 at 10:10 Comment(2)
AFAIK the JNI env is only valid for as long as the function call is active ... You also can't guarantee that the object is still valid. Basically we need to see how you end up in the launchDashboard call. ie from the initial entry to native from java ...Stalag
Why do you have more than one Activity? Have you checked this tutorial: blog.molioapp.com/2011/11/…Casseycassi
B
5

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.

But answered 24/4, 2012 at 16:54 Comment(1)
I'm pretty sure what you say is right for android too ... Isn't there a way to hold an object (ie stop it from getting GC'd from another thread too?)Stalag
S
3

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.

Shoulders answered 24/4, 2012 at 13:41 Comment(4)
@Stalag Maybe, but the code you provided is still buggy. You are saving a JNIEnv pointer in your JNIManager class, and then using this pointer in the launchDashboard() method. If that method is invoked by another thread or even another JNI function, then you will get appcrash. Even though the problem seems to be related to cocos or OpenFeint, you (I guess) need to initialize these libraries with some JNI structures (JNIEnv pointer, perhaps). If you pass an invalid pointer, then the app will crash inside the library.But
It is not me in the question. My code implementation is different.Shoulders
then maybe you could post the code you actually have problem with.But
I am creating a new question, it would be best. #10315289Shoulders

© 2022 - 2024 — McMap. All rights reserved.