2014-02-20 155 views
1

在某些C庫中存在具有簽名的函數:int someFunction(int a);從JNI調用Java方法

我需要從該函數(從本機代碼)調用Java方法,以免破壞接口。 例如:

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

不能把任何額外的參數,像JNIEnvJNI需要someFunction所以我聲明全局變量和函數初始化:

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函數,然後調用一些函數。

它不是容易出錯的方式嗎?多線程怎麼樣?有更好的解決方案嗎?

+1

我不認爲你可以指望你複製到全局值的那些值在返回給它們的函數後保持有效。在我自己的類似的情況下,也是在Android上,我遇到了問題,當我停止將指針存儲爲全局變量並且更新它們並將JVM調用到模塊中時,我解決了這個問題。 – mah

+1

您應該可以緩存vm指針,該指針不會更改,但您需要獲得'JNIEnv'來確保多線程兼容性,就像您上面所述。 – Samhain

+0

如果我正在處理單線程應用程序,可以對JNIEnv全局進行一次初始化,然後在多個jni方法調用(在一個線程中)中使用它。 – Lighter

回答

0

我知道這是舊的,但人們仍然從搜索中找到它。

下面是完整的解決方案,如何從Java調用C代碼,並從JNI的C代碼回調Java代碼:

項目中的JNI目錄

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) 

創建應用程序創建Android.mk。 MK在您的項目JNI目錄

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

項目中的JNI DIR生成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建造

編譯庫libtest.so

創建:MyInterface.java在Java的Android項目的src

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); 


} 

}