5

当我想通过 C++ 代码中的 JNI 调用更改我的 Android 应用程序的 Activity 时,我遇到了一个大问题。App 使用 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 更改为任何自己的 Activity 时,我也会遇到 App-Crash。

但是,当我简单地使用 OpenFeint 通过 JNI 调用更新成就时,我得到一个 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 在异步执行某些操作(更新成就)或更改 Activity 并结合使用 NDK(我使用 NDKr7,但在 NDKr5 上相同)时会出现一些问题.

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

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

谢谢。

4

2 回答 2

5

我不知道 Dalvik 的实现,但@Goz 是对的:JNIEnv 指针的范围仅适用于您调用的 JNI 函数的持续时间。如果您想从本机代码回调到 Java,您不能只保存上一次调用中的 JNIEnv,因为该调用可能不再有效(因此应用程序崩溃)。如果你只有一个线程做所有事情,那么它会起作用。

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

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

然后,您可以使用此对正在运行的虚拟机的引用来获取有效的 JNIEnv 指针,方法是调用:

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

然后你就可以使用环境了,当你完成后,别忘了打电话

jvm->DetachCurrentThread();

附加/分离将导致 Java 向 Thread 对象注册调用者(可以是本机线程),这允许将其视为 Java 线程。

免责声明:至少,这是您在桌面 Java 中的做法。不知道 Dalvik 的实现,但从外观上看,他们只是复制了该技术。

于 2012-04-24T16:54:46.763 回答
3

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

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

于 2012-04-24T13:41:25.210 回答