Android 上的 JNI:使用 Integer 参数从本机回调有效,使用 String 或 Void 则无效。为什么?

JNI on Android: Callback from native with Integer parameter works, with String or Void it doesn't. Why?

我有一个回调 class 用于执行从本机 C++ 代码到 Kotlin 的回调(不确定 Kotlin/Java 在这里是否有所不同,如果有的话,是否有相关文档?)。 我有一个带有整数参数的工作回调,我从不同的本机线程调用它没有问题。现在我想添加第二个发送字符串的,但由于某些原因不起作用。

我的回调 class 实现如下所示:

 jclass target;
 jmethodID id;

 Callback::Callback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
     JNIEnv *g_env;
     int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);

     if (g_env != NULL) {
         target = g_env->GetObjectClass(g_object);
         id = g_env->GetMethodID(target, "integerCallback", "(I)V");
     }
 }

 void Callback::call(int integerValue, const char *stringValue) {
     JNIEnv *g_env;
     int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);

     if (getEnvStat == JNI_EDETACHED) {
         if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
             LOGD("GetEnv: Failed to attach");
         }
     } else if (getEnvStat == JNI_OK) {
        LOGD("GetEnv: JNI_OK");
     } else if (getEnvStat == JNI_EVERSION) {
        LOGD("GetEnv: version not supported");
     }

     g_env->CallVoidMethod(g_object, id, (jint) integerValue);
 }

它在我的 native-lib.cpp 中像这样实例化:

extern "C" {


std::unique_ptr<Callback> callback;

JavaVM *g_jvm = nullptr;

static jobject myJNIClass;


jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
    g_jvm = pJvm;
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL
Java_com_my_app_common_jni_JniBridge_loadNative(JNIEnv *env, jobject instance,
                                                          jstring URI, jboolean isWaitMode) {
    myJNIClass = env->NewGlobalRef(instance);

    if (callback == nullptr) {
        callback = std::make_unique<Callback>(*g_jvm, myJNIClass);
    }

}

它调用的回调方法在我的 JniBridge.kt 中(线程部分可能与问题无关):

object JniBridge {
    init {
        System.loadLibrary("native-lib")
        Timber.d("Native Lib loaded!")
    }

    fun load(fileName: String, isWaitMode: Boolean) {
        loadNative(fileName, isWaitMode)
    }

    fun integerCallback(value: Int) {
        someListener?.invoke(value)
    }

    private external fun loadNative(fileName: String, isWaitMode: Boolean)
}

所以现在,如果我的本机代码在我的回调中触发 call() 方法,我在 JniBridge.kt 中的 integerCallback() 将使用整数正确调用。

但这是我没有得到的:如果我更改我的回调以发送字符串,它不起作用。如果我这样改变它:

// in JniBridge.kt
 fun stringCallback(value: String) {
        someListener?.invoke(value)
    }
// in Callback.cpp

//getting the method
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String)V");

//making the call
g_env->CallVoidMethod(g_object, id, (jstring) stringValue);

现在我的应用因以下错误而崩溃:

JNI DETECTED ERROR IN APPLICATION: JNI GetStringUTFChars called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/my/app/common/jni/JniBridge;.stringCallback(Ljava/lang/String)V"

如果我尝试调用 void 方法(例如:id = g_env->GetMethodID(target, "voidCallback", "(V)V");或调用一个采用两个整数的方法(例如:id = g_env->GetMethodID(target, "twoIntegerCallback", "(I;I)V");,当然有JniBridge.kt中相应的方法到位。

为什么会发生这种情况,我该如何解决?

注意:为了清楚起见,我省略了我认为与问题无关的所有代码部分,如果缺少重要内容,请告诉我,我会修复它。

您传递给 GetMethodID 的方法签名中缺少分号。

应该是:

id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String;)V");

注意 Ljava/lang/String 后的分号。

请参阅 Oracle's JNI documentation 中有关类型签名的部分。


关于您的其他变体:

A Java void function() 将具有签名 ()V.
Java void function(int i, int j) 将具有签名 (II)V.


附带说明一下,您真的应该在每次 JNI 调用后验证 return 值并检查异常。