3

我正在尝试使用 Android 中的 JNI 从我的 C 代码中调用 Java 函数,但我的处境有些尴尬。

我的 C 代码在传递给库的回调中的 JNI 函数之外执行。

这是一个java代码的例子

package com.my.java.package;

class MyClass {
    public function handleData(byte[] data) {
        doSomethingWithThisData(data);
    }
}

这是C代码的示例

void handleData(uint8_t *data, size_t len) {
    // I need to call handleData in my java
    // class instance from here, but i have
    // no access to a JNIEnv here.

    // I don't think I can create one, since 
    // it has to be the same object that's 
    // sending JNI calls elsewhere.
}

. . . 

myCLibInstance.callback = handleData;

现在只要 C Lib 做它需要做的事情,它就会触发回调。但是我没有办法将它发送回java类来处理数据。

4

2 回答 2

2

在某些版本的 Android NDK 上,JNI_GetCreatedJavaVMs可用于获取当前 VM。但是,更好的选择是覆盖JNI_OnLoad并保存 VM。使用任何一种方法,一旦你有了虚拟机,你就可以附加到当前线程并调用函数..

extern jint JNI_GetCreatedJavaVMs(JavaVM **vm, jsize size, jsize *size2);

static JavaVM *jvm = NULL;


static jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    jvm = vm;
    JNIEnv *env = NULL;

    if (jvm && (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6) == JNI_OK)
    {
        return JNI_VERSION_1_6;
    }
    return -1;
}

JavaVM* getJavaVM() {
    if (jvm)
    {
        return jvm;
    }

    jint num_vms = 0;
    const jint max_vms = 5;
    JavaVM* vms[max_vms] = {0};
    if (JNI_GetCreatedJavaVMs(vms, max_vms, &num_vms) == JNI_OK)
    {
        for (int i = 0; i < num_vms; ++i)
        {
            if (vms[i] != NULL)
            {
                return vms[i];
            }
        }
    }
    return NULL;
}

 void handleData(uint8_t *data, size_t len) {
    JavaVM *jvm = getJavaVM();

    if (jvm)
    {
        JNIEnv *env = NULL;
        if ((*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL)  == JNI_OK)
        {
            if (env)
            {
                //Call function with JNI..
            }

            (*jvm)->DetachCurrentThread(jvm);
        }
    }
}
于 2017-06-09T00:30:58.823 回答
0

我注意到Brandons 解决方案存在一些问题,特别是围绕您处理状态代码和不必要getJavaVM功能的方式,因此我进行了一些更改并添加了一些注释。这是我设法开始工作的唯一功能版本。

请注意,由于某种原因,当从另一个线程使用时JNIEnv*,返回的 bygetJNIEnv()不适用于 Java 类加载器。我不确定为什么。所以在这个例子中,我将静态实例存储到类中,直接在函数中加载它们,并在以后需要时使用它们。JNI_OnLoad

如果有人知道一种解决方法来获得JNIEnv*返回getJNIEnv()以支持来自其他线程的 Java 类加载器,请告诉我。

// JavaVM instance stored after JNI_OnLoad is called
JavaVM* javaVM = NULL;

// Since the class loader will not work with getJNIEnv(),
// you can store classes in GlobalRefs.
static jclass my_class_class;

/**
 * Load the JNIEnv and store the JavaVM instance for ater calls to getJNIEnv().
 *
 * @param jvm      The Java VM
 * @param reserved Reserved pointer
 *
 * @return         The supported version of JNI.
 */
JNIEXPORT jint JNICALL
JNI_OnLoad(
        JavaVM* jvm,
        void* reserved
) {
    javaVM = jvm;

    // Here we load the classes since getJNIEnv() does 
    // not work with the class loader from other threads
    JNIEnv* env = getJNIEnv();
    my_class_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/my/java/package/MyClass"));

    // Return the supported JNI version.
    return JNI_VERSION_1_6;
}

/**
 * Retrieve an instance of JNIEnv to use across threads.
 *
 * Note that the class loader will not work with this instance (unsure why).
 *
 * @return a JNIEnv instance
 */
JNIEnv* getJNIEnv() {
    JNIEnv *env;

    // If the current thread is not attached to the VM, 
    // sets *env to NULL, and returns JNI_EDETACHED.
    //
    // If the specified version is not supported, sets *env to NULL, 
    // and returns JNI_EVERSION.
    //
    // Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
    int status = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);

    // Check if the JVM is not currently attached to the
    // calling thread, and if so attempt to attach it.
    if (status == JNI_EDETACHED) {
        // Attaches the current thread to a Java VM.
        // Returns a JNI interface pointer in the JNIEnv argument.
        status = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
    }

    // If the result of GetEnv was JNI_EVERSION,
    // we want to abort.
    assert(status != JNI_EVERSION);

    // Return the ENV if we have one
    return env;
}

void handleData(uint8_t *data, size_t len) {
    JNIEnv* env = getJNIEnv();

    // ... call jni function using env and my_class_class ...
}
于 2020-01-30T21:47:54.513 回答