【问题标题】:Call Java method from JNI从 JNI 调用 Java 方法
【发布时间】:2014-02-20 11:46:02
【问题描述】:

在某些 C 库中存在带有签名的函数:int someFunction(int a);

我需要从此函数(从本机代码)调用 Java 方法,以免破坏接口。 例如:

int someFunction(int a) {
    ...
    jbyteArray result = (jbyteArray)(*jEnv)->CallStaticObjectMethod(jEnv, clazz, methodId);
    ...
}

不能someFunction 中添加JNI 所需的任何额外参数,例如JNIEnv,因此我声明了用于初始化的全局变量和函数:

jmethodID globalMethodId;
jclass globalClass;
JavaVM* globalJVM;

void initJNI(JNIEnv * env, jclass clazz) {
    globalClass = (*env)->NewGlobalRef(env, clazz);
    (*env)->GetJavaVM(env, &globalJVM);

    globalMethodId = (*env)->GetStaticMethodID(env, clazz, "javaMethodName", "()[B");
}

JNIEnv * GetJniEnv() {
    JNIEnv * env;
    if((*globalJVM)->AttachCurrentThread(globalJVM, &env, NULL) != JNI_OK) {
        env = NULL;
    }
    return env;
}

而我的someFunction函数如下

int someFunction(int a) {
    JNIEnv * jEnv = GetJniEnv();
    jbyteArray result = (jbyteArray)(*jEnv)->CallStaticObjectMethod(jEnv, globalClass, globalMethodId);
    ...
}

并且这个新库的用户必须调用 initJNI 函数,然后调用 someFunction。

这不是容易出错的方式吗?多线程呢?有没有更好的解决方案。

【问题讨论】:

  • 我认为你不能指望那些复制到全局变量的值在返回给它们的函数后仍然有效。在我自己的类似情况下,也在 Android 上,当我停止将指针存储为全局变量并更新它们并为 JVM 调用我的模块的时间时,我遇到了问题。
  • 你应该可以缓存 vm 指针,这不会改变,但你需要获得JNIEnv 以确保多线程兼容性,就像你上面一样。
  • 如果我正在处理单线程应用程序,我可以一次性初始化 JNIEnv 全局,然后将其与 jni 方法的多次调用一起使用(在一个线程中)。

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


【解决方案1】:

我知道这是旧的,但人们仍然可以通过搜索找到它。

这里是完整的解决方案,如何从java调用C代码,以及从jni c代码回调java代码:

在你的项目 jni 目录中创建 Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ALLOW_UNDEFINED_SYMBOLS=false
LOCAL_MODULE := testjni
LOCAL_SRC_FILES := testjni.c
LOCAL_LDLIBS    := -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)

在你的项目 jni 目录中创建 Application.mk

APP_ABI := armeabi-v7a
APP_PLATFORM := android-8

在你的项目 jni 目录中创建 testjni.c

#include <jni.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>

#include <android/log.h>

#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,"TAG",__VA_ARGS__)

JavaVM *myVm;

static void *android_gui = NULL;

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    // Keep a reference on the Java VM.
    myVm = vm;

    LOGD("JNI interface loaded.");

    return JNI_VERSION_1_2;
}


void jni_setDataEnv(JNIEnv *p_env, int width, int height)
{
    if (android_gui == NULL)
        return;

    jclass cls = (*p_env)->GetObjectClass (p_env, android_gui);

    jmethodID methodId = (*p_env)->GetMethodID (p_env, cls, "setData", "(II)V");

    //add or delete II's depinding your number or variable passsed

    (*p_env)->CallVoidMethod (p_env, android_gui, methodId, width, height);

    (*p_env)->DeleteLocalRef(p_env, cls);
}

void jni_setData(int width, int height)
{

    JNIEnv *p_env;

    (*myVm)->AttachCurrentThread (myVm, &p_env, NULL);

    jni_setDataEnv(p_env, width, height);

    int check = (*myVm)->GetEnv(myVm, (void *) &p_env, JNI_VERSION_1_2);

    LOGD(" Detach destructor = %d", check);

    if (check != JNI_EDETACHED)
     {

        int detach = (*myVm)->DetachCurrentThread (myVm);

        LOGD("Detach result ==: %d", detach);

     }


}

//change this to your package

JNIEXPORT void JNICALL Java_com_example_test_LibTest_initM(JNIEnv *env, jobject thiz, jobject gui)
{

   android_gui = (*env)->NewGlobalRef(env, gui);

}


//when you need , in your c code call jni_setData(100.200 ) and
//MainActivity method , setData() will receive this values

void *call_from_thread()
{
    jni_setData(1,2);
    return NULL;
}

JNIEXPORT void JNICALL Java_com_example_test_LibTest_test(JNIEnv *env, jobject thiz)
{


    pthread_t t;
    //do hard work in a thread
    pthread_create(&t, NULL, call_from_thread, NULL);


}

在 jni 文件夹中运行 ndk-build 编译库 libtest.so

在您的 java android 项目 src 中创建:MyInterface.java

package com.example.test;

public interface MyInterface
{

    void setData(int width, int height);

}

创建LibTest.java //用于从java向C代码发送数据

package com.example.test;

public class LibTest 
{

  static 
  {
     System.loadLibrary("testjni");
  }

  private static LibTest sInstance;

  public static LibTest getInstance() 
   {
        synchronized (LibTest.class) 
          {
            if (sInstance == null) 
              {
                /* First call */
                sInstance = new LibTest();
              }
           }

        return sInstance;
   }

   public native void initM( MyInterface myinterface );

   public native void test();

}

创建 MainActivity.java

package com.example.test;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity implements MyInterface 
{
    int mHeight;
    int mWidth;

    LibTest libtest;

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        libtest = LibTest.getInstance();

        libtest.initM( MainActivity.this );


        //tell jni to send us some data
        //we receive data in 'setData(int width, int height)' method

        libtest.test();

    }

@Override
public void setData(int width, int height) 
{
    /// Here you receive data from C code 
    //We can store the values C send to Java so we use latter

    mHeight = height;
    mWidth  = width;

    Log.d("TAG" ,"GOT SOME DATA FROM C CODE" + mHeight + " " + mWidth );


}

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多