【问题标题】:Calling JAVA class member from Native C/C++ code从 Native C/C++ 代码调用 JAVA 类成员
【发布时间】:2011-11-08 06:25:03
【问题描述】:

我正在编写一个 OpenGL C/C++ 应用程序,我通过 Android NDK、JNI 支持将它移植到 Android。我在执行来自本机信号的 JAVA 回调的代码时遇到困难。

代码如下:

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

在本机代码中,我有一个函数 texturesLoaded(),当某些纹理完全加载到另一个线程上时会发出信号,我需要从 JAVA 端的 nativeLib.draw(...) 强制刷新。这是我的做法:

我在 JNI_OnLoad 和 gJObjectCached 上缓存 JavaVM、jClass、jMethodID

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

然后在 texturesLoaded() 上做:

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

结果...从本机端我得到了类,我得到了方法,方法被调用了,但是当它到达/调用内部的 requestRender() 时(或试图访问 GLSurfaceView 的任何其他成员方法,它崩溃了!)

我无法尝试 CallStaticVoidMethod(gJClass, gjMethodID);因为那时我无权访问 requestRender();

任何想法或意见,也许我在这里做错了。

谢谢

【问题讨论】:

  • requestRender() 在 Java 中,因此崩溃通常伴随着异常。你能提到那个例外是什么吗? Android ADB 日志应显示此类信息。去Here了解日志

标签: android java-native-interface android-ndk


【解决方案1】:

您需要为您隐藏的类/对象创建全局引用。您保存的引用是本地引用,它们不能跨线程共享,并且在运行时清理 JNI 本地引用堆栈时消失。

查看Sun/Oracle documentation for global and local references,并查看JNI 方法JNIEnv::NewGlobalRefJNIEnv::DeleteGlobalRef

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

gJObjectCached = env->NewGlobalRef(obj);

(编辑:原来您不需要方法 ID 的全局引用。)

【讨论】:

  • 您的回答非常有用,但对我的问题没有帮助。我的全局缓存 jclass 和 jmethodID 是有效的,我可以访问我的 JAVA 回调,它是该类的成员(它不是静态的)。问题是我无法从该回调中调用该类的另一个成员,因为我认为我缓存的对象没有指向:“com/android/newlineactivity/NewLineGLSurfaceView”,因为我可能将它缓存在另一个 JENV 中。因此,对于静态成员,这是有效的: env->CallStaticVoidMethod(gJClass, gJMethodID);但对于非静态的,这不是: env->CallVoidMethod(gJObjectCached, gJMethodID);有意义吗?
  • gJObjectCached 是全局引用,还是只是存储了传入cachejavaobject 的本地引用?
  • 它是一个全局引用,在我调用 env->CallVoidMethod(gJObjectCached, gJMethodID); 时有效但是,当它在内部调用 requestRender() 时,回调()会崩溃。我什至尝试了 jobject jobj = env->NewObject(gJClass, gJMethodID);并通过它,希望使用正确的 env、gjClass 和 gJMethodID 我可以调用该方法并且它会起作用...方法被调用但在 requestRender() 处再次崩溃;
  • 嗯......你的问题有点令人困惑...... jobject gJObjectCached;在我的 .cpp 文件中被声明为全局的,我在其中存储了传递给 cachejavaobject 的本地引用,使用: JNIEXPORT void Java_com_android_OpenGL_NativeLib_cachejavaobject(JNIEnv env, jobject obj) { // 缓存 java 对象 gJObjectCached = env ->NewGlobalRef(obj); LogNativeToAndroidExt("gjObjectCached global"); }
  • 如果你正在做gJObjectCached = env-&gt;NewGlobalRef(obj),那么你正在存储一个全局引用,这是你应该做的。如果这样做后仍然无法正常工作,恐怕我没有建议了。
猜你喜欢
  • 1970-01-01
  • 2012-04-28
  • 2018-09-27
  • 2010-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多