Изменение активности с помощью JNI Call или использование Openfeint вызывает App-Crash - PullRequest
5 голосов
/ 08 марта 2012

У меня огромная проблема, когда я хочу изменить активность моего Android-приложения с помощью вызова JNI из моего кода C ++. Приложение использует cocos2d-x для рендеринга. Конкретная ситуация заключается в том, что я хочу открыть OpenFeint-Dashboard в Java, используя эту очень маленькую функцию:

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

Эта функция затем вызывается из C ++ с помощью простого вызова JNI:

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, потому что я также получаю 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 есть некоторые проблемы при асинхронном выполнении чего-либо (обновление достижений) или при изменении действия в сочетании с использованием NDK (я использую NDKr7, но то же на NDKr5).

Вы также должны знать, что у меня уже есть некоторые другие функции, определенные в Java, которые вызываются с помощью вызова JNI и которые работают правильно!

Может быть, я сделал что-то не так, Может кто-нибудь дать мне совет по этому или рабочему примеру кода, как изменить активность. Может быть, это проблема с Cocos2d-x.

Спасибо.

Ответы [ 2 ]

5 голосов
/ 24 апреля 2012

Я не знаю о реализации Dalvik, но @Goz прав: область видимости указателя JNIEnv только для продолжительности вызываемой вами функции JNI. Если вы хотите сделать обратный вызов из нативного кода в Java, вы не можете просто сохранить JNIEnv из предыдущего вызова, потому что он может быть недействительным (отсюда appcrashes). Если бы у вас был только один поток, выполняющий все, тогда он работал бы.

Что вам нужно сделать (если у вас несколько потоков), так это получать действительный указатель JNIEnv при каждом обратном вызове. В функции инициализации вы сохраняете указатель на текущую запущенную виртуальную машину:

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

Затем вы можете использовать эту ссылку на работающую виртуальную машину, чтобы получить действительный указатель JNIEnv, вызвав:

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

Тогда вы можете работать с env, и когда вы закончите, не забудьте позвонить

jvm->DetachCurrentThread();

Присоединение / отсоединение заставит Java зарегистрировать вызывающего (который может быть собственным потоком) с объектом Thread, что позволяет рассматривать его как поток Java.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: По крайней мере, это то, как вы делаете это в настольной Java. Не знаю о реализации Dalvik, но, судя по всему, они просто скопировали технологию.

3 голосов
/ 24 апреля 2012

Я нашел ответ для своего дела. Это было легко исправить, но сложно найти. Когда приложение переключается на новое действие, вызывается метод nativeOnPause из cocos2d-x MessageJNI. Этот метод должен вызывать CCApplication :: sharedApplication (), но один из моих классов ранее называл деструктор CCApplication, который очищал общий синглтон до нуля.

РЕДАКТИРОВАТЬ: Это полное редактирование исходного сообщения, поэтому комментарии больше не имеют смысла.

...