JNI使用接口回调到Java

6
首先,让我列出我能找到的最佳结果。jni调用以自定义java接口为参数的java方法 这个回答并不能解决我的问题。让我解释一下我的问题。我想要按如下方式调用NDK。
(1)Java -> (2)CPP -> (3)C (新线程) -> (4)CPP -> (5)Java
代码如下。
(1) Java
public interface Callback<T> {
    void success(T result);
}
private native void jniUploadAsync(String imagePath, Callback<String> callback);

jniUploadAsync(file.getAbsolutePath(), new Callback<String>() {
                                        @Override
                                        public void success(final String result) {
                                            Log.v("MyClass: result:: ", result);
                                        }
                                    });

(2) CPP

static JavaVM *jvm;
void imageUploadCallback(char *json, void *completionCallback) {
    JNIEnv *env;
    jint rs = jvm->AttachCurrentThread(&env, NULL);//create JNIEnv from JavaVM    
    jclass cbClass = env->FindClass("org/winster/test/Callback");
    jmethodID method = env->GetMethodID(cbClass, "success", "(Ljava/lang/String;)V");
    env->CallVoidMethod(static_cast<jobject>(completionCallback), method, "abcd");
}

void Java_org_winster_test_MyClass_jniUploadAsync(JNIEnv * env, jobject obj, jstring imagePath, jobject completionCallback) {
    jint rs = env->GetJavaVM(&jvm); //Cache JavaVM here
    CallMyCMethod((char *)filePath, &imageUploadCallback, &completionCallback);
}

(3) C

CallMyCMethod() //please assume that it works. The reason I need void* as the type for completionCallback is because, in ObjC implementation I use this

(4) CPP

//Call comes back to imageUploadCallback()

(5) Java

//I expect this Log.v("MyClass: result:: ", result); to be executed

请注意,这不是关于如何从C++调用Java的基础问题。我想解决的两个具体问题是如何调用“回调”以及如何调用Java接口实现中的方法。我已经在Obj-C中完成了这个过程,那里很简单直接。

你遇到了什么错误? - V-master
1
imageUploadCallback中,env的来源不清楚。很可能需要调用AttachCurrentThread - Michael
JNI在应用程序中检测到错误:使用无效的jobject。当我尝试调用“callback”时,就会出现这个错误。 - Winster
1个回答

8

(2) 首先,您需要存储对 JavaVM 的引用,以便稍后可以从其他线程获取 JNIEnv。此外,您需要从参数中获取的局部变量获取新的全局引用(在不再需要时,请勿忘记删除它,否则会导致内存泄漏)。

static JavaVM* jvm = 0;

void Java_org_winster_test_MyClass_jniUploadAsync(JNIEnv * env, jobject obj, jstring imagePath, jobject completionCallback) {
    env->GetJavaVM(&jvm); //store jvm reference for later

    jobject globalref = env->NewGlobalRef(completionCallback);

    CallMyCMethod((char *)filePath, &imageUploadCallback, (void *)globalref);
}

(4)当使用泛型时,本地端不知道它们的类型,因此在 JNI/C/CPP 部分中使用 T 的地方应该使用 Object

您正在 C 中启动新线程。如果您希望从该线程触发回调,则需要将其连接到 Java 虚拟机并在此之后分离它。根据我的理解,您仅使用回调对象一次。在这种情况下,您还需要删除对它的全局引用。

void imageUploadCallback(char *json, void *completionCallback) {
    JNIEnv* env;
    jvm->AttachCurrentThread(&env, NULL);

    jclass cbClass = env->FindClass("org/winster/test/Callback");
    jmethodID method = env->GetMethodID(cbClass, "success", "(Ljava/lang/Object;)V");
    jstring abcd = env->NewStringUTF("abcd");
    env->CallVoidMethod(completionCallback, method, abcd);

    env->DeleteGlobalRef(completionCallback);
    jvm->DetachCurrentThread();
}

抱歉,这并没有帮助。当然我会缓存环境变量;我以为这是可以理解的。正如所指出的,我在这里尝试解决的问题是:当您拥有接口作为类型的实例时,如何在Java中调用回调函数。调用常规类方法并从本地线程调用它们是容易解决的事情。 - Winster
我还添加了在jni中使用全局引用,我之前忘记了。这应该是你在评论中提到的“使用无效jobject”的情况。 - V-master
仍然不能工作。示例代码有两个更改。 1)替换(void *),我使用了“&”。 CallMyCMethod((char *)filePath, &imageUploadCallback, &globalref); 2)从void *转换回jobject env->CallVoidMethod(static_cast<jobject>(completionCallback), callback_mid, jsonString); 相同的错误“使用无效的jobject”。 - Winster
我创建了一个快速示例(在我的情况下,我使用现有的qtandroid应用程序)https://pastebin.com/00CBp1PK,并且它运行得非常好。我使用了这个答案中的所有内容+粘合代码。 - V-master
对于那些盲目复制的人,需要额外注意的是:只有在线程不是Java线程而是C (或其他类型) 线程时,才应该使用 DetachCurrentThread 分离一个线程。例如请查看:https://dev59.com/E67la4cB1Zd3GeqPcWhf#52342198 - Hendrik
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接