使用JNI调用或使用Openfeint更改活动会导致应用程序崩溃

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

本文关键字:活动 崩溃 应用程序 调用 JNI Openfeint 使用      更新时间:2023-10-16

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

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

然后用一个简单的JNI调用从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调用更改活动时存在一些循环问题,因为在将活动更改为任何自己的活动时,我也会遇到应用程序崩溃。

但是,当我简单地使用OpenFeint通过JNI调用更新成就时,我也会遇到应用程序崩溃,类似于更改活动时:

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在异步执行某些操作(更新Achievement)或结合使用NDK更改"活动"(我使用NDKr7,但在NDKr5上也是如此)时存在一些问题。

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

也许我做错了什么,有人能给我一些关于这方面的建议吗?或者给我一个如何更改活动的工作示例代码。也许是Cocos2d-x的问题。

谢谢。

我不知道Dalvik的实现,但@Goz是对的:JNIEnv指针的作用域只是您调用的JNI函数的持续时间。如果你想从本机代码回调到Java,你不能只保存上一次调用的JNIEnv,因为那个调用可能不再有效(因此appcrash)。如果你每件事都只有一个线程,那么它就会起作用。

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

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

然后,您可以使用对正在运行的虚拟机的引用,通过调用来获得有效的JNIEnv指针

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

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

jvm->DetachCurrentThread();

附加/分离将导致Java向thread对象注册调用方(可以是本机线程),从而将其视为Java线程。

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

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

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