Изменение действия с помощью вызова JNI или использование Openfeint вызывает сбой приложения

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

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

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

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

Спасибо.


person Andy Reimann    schedule 08.03.2012    source источник
comment
AFAIK env JNI действителен только до тех пор, пока активен вызов функции... Вы также не можете гарантировать, что объект все еще действителен. По сути, нам нужно посмотреть, как вы окажетесь в вызове launchDashboard. т.е. из начальной записи в родную из java...   -  person Goz    schedule 21.04.2012
comment
Почему у вас больше одного Activity? Вы проверили это руководство: blog.molioapp.com/ 2011/11/   -  person Macarse    schedule 24.04.2012


Ответы (2)


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

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

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

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

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

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

jvm->DetachCurrentThread();

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

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

person Jakub Zaverka    schedule 24.04.2012
comment
Я почти уверен, что то, что вы говорите, подходит и для Android... Нет ли способа удержать объект (т.е. не дать ему получить GC из другого потока?) - person Goz; 25.04.2012

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

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

person MLProgrammer-CiM    schedule 24.04.2012
comment
@Goz Возможно, но предоставленный вами код все еще содержит ошибки. Вы сохраняете указатель JNIEnv в своем классе JNIManager, а затем используете этот указатель в методе launchDashboard(). Если этот метод вызывается другим потоком или даже другой функцией JNI, вы получите сбой приложения. Несмотря на то, что проблема, похоже, связана с cocos или OpenFeint, вам (я предполагаю) необходимо инициализировать эти библиотеки некоторыми структурами JNI (возможно, указателем JNIEnv). Если вы передадите недопустимый указатель, приложение рухнет внутри библиотеки. - person Jakub Zaverka; 25.04.2012
comment
Это не я в вопросе. Моя реализация кода отличается. - person MLProgrammer-CiM; 25.04.2012
comment
тогда, возможно, вы могли бы опубликовать код, с которым у вас действительно есть проблема. - person Jakub Zaverka; 25.04.2012
comment
Я создаю новый вопрос, это было бы лучше. stackoverflow.com/ вопросы/10314789/ - person MLProgrammer-CiM; 25.04.2012