2012-03-08 9 views
5

Ho un grosso problema quando voglio modificare l'attività della mia applicazione Android con una chiamata JNI dal mio codice C++. L'app utilizza cocos2d-x per il rendering. La situazione concreta è che voglio aprire l'OpenFeint-Dashboard in Java utilizzando questo molto piccola funzione:Modifica attività con chiamata JNI o ​​utilizzo di Openfeint causa App-Crash

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

Questa funzione viene poi chiamato da C++ con un semplice JNI-Call:

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

l'implementazione JNIManager classe è anche molto semplice e basilare:

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

Dal mio punto di vista, cocos2d-x ha qualche problema quando si cambia la cirical attività con una chiamata JNI, perché ho anche un App-Crash quando si cambia l'attività in qualsiasi attività.

MA, anche quando semplicemente uso OpenFeint per aggiornare un risultato con un JNI chiamata ottengo un App-Crash, simile a quando si cambia l'attività:

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

questo mi porta ad un punto su quello che direbbe, che Android o Cocos2d-x ha qualche problema quando si fa qualcosa in modo asincrono (aggiornamento Achievement) o quando si modifica l'attività in combinazione con l'utilizzo di NDK (io uso NDKr7, ma lo stesso su NDKr5).

Si dovrebbe anche sapere che ho già alcune altre funzioni definite in Java che vengono chiamate con una chiamata JNI e che funzionano correttamente!

Forse ho fatto qualcosa di sbagliato, qualcuno può darmi qualche consiglio su questo o un codice di esempio di lavoro su come modificare l'attività. Forse è un problema con Cocos2d-x.

Grazie.

+2

AFAIK l'indirizzo JNI è valido solo finché la chiamata di funzione è attiva ... Inoltre, non è possibile garantire che l'oggetto sia ancora valido. Fondamentalmente abbiamo bisogno di vedere come finisci nella chiamata launchDashboard. cioè dalla voce iniziale a nativo da java ... – Goz

+0

Perché hai più di un 'attività'? Hai controllato questo tutorial: http://blog.molioapp.com/2011/11/openfeint-and-admob-integrated-with.html – Macarse

risposta

3

Ho trovato la risposta per il mio caso. Era facile da risolvere ma complesso da trovare. Quando l'app si trasforma in una nuova attività, viene chiamato il metodo nativeOnPause da cocos2d-x MessageJNI. Questo metodo dovrebbe chiamare un CCApplication :: sharedApplication(), ma una delle mie classi aveva precedentemente chiamato il distruttore CCApplication, che cancellava il singleton condiviso in null.

EDIT: Questa è una modifica completa dal post originale, quindi i commenti non hanno più senso.

+0

@ Go Forse, ma il codice che hai fornito è ancora buggato. Si sta salvando un puntatore JNIEnv nella classe JNIManager e quindi si utilizza questo puntatore nel metodo launchDashboard(). Se quel metodo è invocato da un altro thread o anche da un'altra funzione JNI, otterrai appcrash. Anche se il problema sembra essere correlato a cocos o OpenFeint, tu (credo) devi inizializzare queste librerie con alcune strutture JNI (puntatore JNIEnv, forse). Se passi un puntatore non valido, l'app si blocca all'interno della libreria. –

+1

Non sono io nella domanda. La mia implementazione del codice è diversa. –

+0

allora forse potresti pubblicare il codice con cui hai effettivamente problemi. –

5

Non so sull'implementazione di Dalvik, ma @Goz ha ragione: lo scope per il puntatore JNIEnv è solo per la durata della funzione JNI che si chiama. Se si desidera eseguire la richiamata dal codice nativo a Java, non è possibile salvare JNIEnv dalla chiamata precedente, poiché quella potrebbe non essere più valida (quindi appcrashes). Se avessi un solo thread che fa tutto, allora funzionerebbe.

Quello che devi fare (se hai più thread), è ottenere un puntatore JNIEnv valido ogni volta che stai per richiamare.Nella funzione di inizializzazione, si salva un puntatore al corrente in esecuzione della macchina virtuale:

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

È possibile quindi utilizzare questo riferimento ad una macchina virtuale in esecuzione per ottenere un puntatore MEnv valida chiamando:

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

poi si può lavorare con l'ENV e quando hai finito, non dimenticare di chiamare

jvm->DetachCurrentThread(); 

l'aggancio/sgancio causerà Java per registrare il chiamante (che può essere un na tive thread) con un oggetto Thread, che consente di trattarlo come un thread Java.

DISCLAIMER: Almeno, questo è il modo in cui lo si fa in Java desktop. Non so sull'implementazione di Dalvik, ma dal suo aspetto, hanno appena copiato la tecnologia.

+1

Sono abbastanza sicuro che quello che dici sia giusto anche per Android ... Non c'è un modo tenere un oggetto (cioè impedirgli di ottenere GC da un altro thread?) – Goz

Problemi correlati