2011-09-01 16 views
6

Escribo una aplicación OpenGL C/C++ que estoy migrando a Android a través de Android NDK, soporte JNI. Tengo dificultades para ejecutar el código de devolución de llamada de JAVA desde el origen.Llamando al miembro de la clase JAVA desde el código nativo C/C++

Aquí está el código:

class OpenGLSurfaceView extends GLSurfaceView 
{ 
… 
    public OpenGLSurfaceView(Context context, int deviceWidth, int deviceHeight) 
    { 
     super(context); 
     nativeObj = new NativeLib(); 
     mRenderer = new OpenGLRenderer(context, nativeObj, deviceWidth, deviceHeight); 
     setRenderer(mRenderer); 
     setRenderMode(RENDERMODE_WHEN_DIRTY); 
    } 
… 
    private void CallBack() 
    { 
     // force redraw 
     requestRender(); 
    } 
} 


class OpenGLRenderer implements GLSurfaceView.Renderer 
{ 
    … 
public void onSurfaceCreated(GL10 gl, EGLConfig config) 
    { 
     nativeObj.init(…); 
     nativeObj.cachejavaobject(JNIEnv *env, jobject obj); // for caching obj on native side 
    } 

    public void onSurfaceChanged(GL10 gl, int w, int h) 
    { 
    } 

    public void onDrawFrame(GL10 gl) 
    { 
     nativeObj.draw(…); 
    } 
} 

Y en código nativo Tengo una función texturesLoaded() que se señala cuando algunas texturas se cargan por completo en otro hilo y tengo que forzar una actualización de nativeLib.draw (...) en el lado de JAVA. Así es como lo hago:

cacheamos la JavaVM, JClass, jMethodID en JNI_OnLoad y gJObjectCached

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) 
{ 
gJVM = jvm; // cache the JavaVM pointer 

LogNativeToAndroidExt("JNILOAD!!!!!!!!!!"); 
JNIEnv *env; 
int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6); 
if(status < 0) 
{ 
    LogNativeToAndroidExt("Failed to get JNI environment, assuming native thread"); 
    status = gJVM->AttachCurrentThread(&env, NULL); 
    if(status < 0) 
    { 
     LogNativeToAndroidExt("callback_handler: failed to attach current thread"); 
     return JNI_ERR; 
    } 
} 

gJClass = env->FindClass("com/android/newlineactivity/NewLineGLSurfaceView"); 
if(gJClass == NULL) 
{ 
    LogNativeToAndroidExt("Can't find Java class."); 
    return JNI_ERR; 
} 

gJMethodID = env->GetMethodID(gJClass, "callback", "()V"); 
if(gJMethodID == NULL) 
{ 
    LogNativeToAndroidExt("Can't find Java method void callback()"); 
    return JNI_ERR; 
} 

return JNI_VERSION_1_6; 

}

JNIEXPORT void Java_com_android_OpenGL_NativeLib_cachejavaobject(JNIEnv* env, jobject obj) 
{ 
    // cache the java object 
    gJObjectCached = obj; 
... 
} 

y luego en texturesLoaded() Yo:

void texturesLoaded() 
{ 
    // Cannot share a JNIEnv between threads. You should share the JavaVM, and use JavaVM->GetEnv to discover the thread's JNIEnv 
    JNIEnv *env = NULL; 
    int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6); 
    if(status < 0) 
    { 
     LogNativeToAndroidExt("callback_handler: failed to get JNI environment, assuming native thread"); 
     status = gJVM->AttachCurrentThread(&env, NULL); 
     if(status < 0) 
     { 
      LogNativeToAndroidExt("callback_handler: failed to attach current thread"); 
      return; 
     } 
    } 

    LogNativeToAndroidExt("Calling JAVA method from NATIVE C/C++"); 
    env->CallVoidMethod(gJObjectCached, gJMethodID); 
    LogNativeToAndroidExt("DONE!!!"); 
} 

Resultado ... del lado nativo obtengo la clase, obtengo el método, se llama al método, pero cuando alcanza/cal ls requestRender() dentro (o tratando de acceder a cualquier otro método miembro de GLSurfaceView se bloquea!)

No puedo probar con CallStaticVoidMethod (gJClass, gjMethodID); porque entonces no tengo acceso a requestRender();

Cualquier idea u opinión, tal vez estoy haciendo algo mal aquí.

Gracias

+0

'requestRender()' es en Java, por lo que los accidentes suelen ir acompañados de una excepción. ¿Podría mencionar cuál es esa excepción? Los registros de ADB de Android deberían revelar dicha información. Vaya [Aquí] (http://developer.android.com/guide/developing/debugging/debugging-log.html) para obtener información sobre los registros – Warpspace

Respuesta

3

Se necesitan crear referencias globales a la clase/objeto que esconder lejos. Las referencias que está guardando son referencias locales, que no se pueden compartir entre subprocesos y desaparecen cuando el tiempo de ejecución limpia la pila de referencia local JNI.

Eche un vistazo a Sun/Oracle documentation for global and local references, y consulte los métodos de JNI JNIEnv::NewGlobalRef y JNIEnv::DeleteGlobalRef.

gJClass = env->NewGlobalRef(env->FindClass(...)); 

gJObjectCached = env->NewGlobalRef(obj); 

(Editar: Resulta que no es necesario referencias globales para los ID de método.)

+0

Su respuesta es muy útil, pero no ayuda mi problema. Mi jclass en caché global y jmethodID son válidos, puedo alcanzar mi devolución de llamada JAVA, que es miembro de esa clase (no es estática). El problema es que no puedo llamar a otro miembro de esa clase desde esa devolución de llamada, porque creo que el jobject i cache no apunta a: "com/android/newlineactivity/NewLineGLSurfaceView" porque probablemente lo guarde en otro JENV. Entonces, para un miembro estático esto funciona: env-> CallStaticVoidMethod (gJClass, gJMethodID); pero para un no estático esto no: env-> CallVoidMethod (gJObjectCached, gJMethodID); ¿Tiene sentido? – eKKo82

+0

¿'gJObjectCached' es una referencia global, o acabas de almacenar la referencia local que se pasó a' cachejavaobject'? –

+0

Es una referencia global y es válida en el momento en que llamo env-> CallVoidMethod (gJObjectCached, gJMethodID); Pero esa devolución de llamada() se bloquea cuando llama a requestRender() internamente.Incluso intenté jobject jobj = env-> NewObject (gJClass, gJMethodID); y pase eso, con la esperanza de que con el env, gjClass y gJMethodID correctos pueda llamar al método y funcionará ... se llama a los métodos pero se bloquea nuevamente en requestRender(); – eKKo82

Cuestiones relacionadas