使用JNI调用或使用Openfeint更改活动会导致App-Crash

时间:2012-03-08 10:10:22

标签: android c++ android-ndk java-native-interface cocos2d-x

当我想用我的C ++代码中的JNI调用来更改Android-Application的Activity时,我遇到了很大的问题。该应用程序使用cocos2d-x进行渲染。 具体情况是我想用Java这个非常小的函数打开OpenFeint-Dashboard:

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

然后使用简单的JNI-Call从C ++调用此函数:

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");
}

JNIManager类的实现也非常简单和基本:

#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;
}

从我的角度来看,cocos2d-x在使用JNI调用更改活动时存在一些循环问题,因为在将Activity更改为任何自己的Activity时,我也会遇到App-Crash。

但是,当我只是使用OpenFeint通过JNI调用更新Achievement时,我得到了一个App-Crash,类似于更改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() + ")");
}

这让我想到了我会说的话,Android或Cocos2d-x在做异步(更新成就)时或者在使用NDK时更改Activity时遇到了一些问题(我使用的是NDKr7,但是在NDKr5上。

您还应该知道我已经在Java中定义了一些其他函数,这些函数通过JNI调用调用并且正常工作!

也许我做错了什么, 有人可以给我一些建议或一个如何改变活动的工作示例代码。也许这是Cocos2d-x的一个问题。

感谢。

2 个答案:

答案 0 :(得分:5)

我不了解Dalvik实现,但@Goz是正确的:JNIEnv指针的范围仅适用于您调用的JNI函数的持续时间。如果你想从本机代码回调到Java,你不能只从前一次调用中保存JNIEnv,因为那个可能不再有效(因此appcrashes)。如果你只有一个线程在做每个人,那么它就可以工作。

你需要做什么(如果你有多个线程),每次你要回调时都要获得一个有效的JNIEnv指针。在初始化函数中,您将指针保存到当前正在运行的虚拟机:

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

然后,您可以通过调用以下方法将此引用用于正在运行的虚拟机以获取有效的JNIEnv指针:

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

然后你可以使用env,当你完成后,不要忘记打电话

jvm->DetachCurrentThread();

attach / detach将导致Java使用Thread对象注册调用者(可以是本机线程),这允许将其视为Java线程。

免责声明:至少,这是你在桌面Java中的表现。不知道Dalvik的实现,但从它的角度来看,他们只是复制了这项技术。

答案 1 :(得分:3)

我找到了我的答案。它很容易修复但很难找到。当应用程序更改为新活动时,将调用cocos2d-x MessageJNI中的nativeOnPause方法。这个方法应该调用CCApplication :: sharedApplication(),但我的一个类之前调用​​了CCApplication析构函数,它将共享单例清除为null。

编辑:这是原始帖子的完整编辑,所以评论不再有意义。