Android JNI里c++调用java时遇到的FindClass返回0的问题
2014-05-27 18:16
1036 查看
我在工作中要解决一个问题就是,从jni底层往java层,抛一个提醒消息,这是不定时的。
发现JNIEnv*,jobject,jclass都是不能保存为全局变量供以后调用的,这跟java虚拟机的机制有关。要在JNI_OnLoad时保存JavaVM*为全局变量。
我原本以为这样的话就可以动态得到jclass和JNIEnv*,以后我再呼叫CallStaticVoidMethod(cls, methodId)这样就可以避开jobject的获取了,jclass用FindClass获得,不用GetObjectClass获得。
结果FindClass返回的总是0,实在令我太无奈了,搞了1天多都没有搞定,后来发现里面有蹊跷。原来jni里面有一个大坑,具体原因见我从百度文库里找到的下面这篇文章,帮助了我解决这一难题。
原文如下:
最后我还在JNI_OnUnload里把那个全局变量的jobject给用DeleteGlobalRef掉。
发现JNIEnv*,jobject,jclass都是不能保存为全局变量供以后调用的,这跟java虚拟机的机制有关。要在JNI_OnLoad时保存JavaVM*为全局变量。
我原本以为这样的话就可以动态得到jclass和JNIEnv*,以后我再呼叫CallStaticVoidMethod(cls, methodId)这样就可以避开jobject的获取了,jclass用FindClass获得,不用GetObjectClass获得。
结果FindClass返回的总是0,实在令我太无奈了,搞了1天多都没有搞定,后来发现里面有蹊跷。原来jni里面有一个大坑,具体原因见我从百度文库里找到的下面这篇文章,帮助了我解决这一难题。
原文如下:
JNI使用示例 2010-03-15 11:18 JNI提供了一种扩展Android功能和移植已有软件的方式。本文将通过一个实例来讲述如何建立JNI库以及JNI库如何与android的JVM交互。 Java接口 定义java类JNIExampleInterface, 该类提供了调用Native库中本地函数的接口。本地函数和对应的Java函数具有相互匹配的签名式(即,参数的类型和个数,以及返回值的类型)。获取本地库中对应的函数签名式的最简单的方法就是,首先写出对应的Java原型,然后使用javah工具生成对应的本地JNI头文件。可以copy/paste到C++文件中来实现对应的函数。 本地函数支撑的对应的Java函数按照正常方式去声明,但需要加上native。我们还想演示如何在native代码中调用Java代码,因此我们的接口类定义如下: package org.wooyd.android.JNIExample; import android.os.Handler; import android.os.Bundle; import android.os.Message; import org.wooyd.android.JNIExample.Data; public class JNIExampleInterface { private Handler h; <Example constructors> <Example native functions> <Example callback> } 为什么要定义Handler呢? 当本地库需要通过callback传递信息给Java进程,如果这个callback是由本地线程调用的,并且想修改应用的用户界面,就会产生exception。这是因为Android仅仅允许主线程更改用户界面。为了避免这个问题,我们使用Handler提供的消息传递接口将callback接收到的数据传递给主线程,让主线程去更改界面。 <Example constructors> public JNIExampleInterface(Handler h) { this.h = h; } 为了阐述不同的参数传递技术,我们定义了三个native函数: callVoid(): 没有参数并且没有返回值 ; getNewData(): 有两个参数,用来构造一个新的类的实例; getDataString(): 用对象作为参数,从对象中抽取值。 <Example native functions> public native void callVoid(); public native Data getNewData(int i, String s); public native String getDataString(Data d); callback接收一个string参数,并将其封装成Bundle后分发给Handler: <Example callback> public static void callBack(String s) { Bundle b = new Bundle(); b.putString("callback_string", s); Message m = Message.obtain(); m.setData(b); m.setTarget(h); m.sendToTarget(); } 另外我们定义一个Data dummy类 Data.java package org.wooyd.android.JNIExample; public class Data { public int i; public String s; public Data() {} public Data(int i, String s) { this.i = i; this.s = s; } } 编译Data.java和JNIExampleInterface.java $ javac org/wooyd/android/JNIExample/*.java 生成JNI头文件,包含与Java对应的本地函数的原型 $ javah -classpath . org.wooyd.android.JNIExample.JNIExampleInterface 本地库的实现 <JNIExample.cpp> <JNI includes> <Miscellaneous includes> <Global variables> #ifdef __cplusplus extern "C" { #endif <callVoid implementation> <getNewData implementation> <getDataString implementation> <initClassHelper implementation> <JNIOnLoad implementation> #ifdef __cplusplus } #endif 头文件和全局变量 下面的include包含了Android的JNI定义的函数: <JNI includes> #include <jni.h> #include <JNIHelp.h> #include <android_runtime/AndroidRuntime.h> 一些其他要用到的头文件: <Miscellaneous includes> #include <string.h> #include <unistd.h> #include <pthread.h> <Global variables> static JavaVM *gJavaVM; static jobject gInterfaceObject, gDataObject; const char *kInterfacePath = "org/wooyd/android/JNIExample/JNIExampleInterface"; const char *kDataPath = "org/wooyd/android/JNIExample/Data"; 从本地线程和java线程调用java函数 callVoid函数是最简单的一个,因为他没有任何参数并且没有返回值。我们用它来说明通过调用Java的callBack函数,如何将数据传回给Java。 至此,我们有必要区分下面两种情况: Java函数可能在Java线程或本地线程中被调用,JVM是无法得知的。对于前者,调用可以直接执行,而对于后者,我们必须首先将本地线程关联到JVM中。因此需要一个附加层,本地回调句柄(Native CallBack Handler),来正确处理这两种情况。我们还需要一个建立本地线程的函数,因此实现如下: <callVoid implementation>= <Callback handler> <Thread start function> <callVoid function> Native callback handler获取JNI环境(如果需要,则关联本地线程),使用缓存的全局对象gInterfaceObject获取JNIExampleInterface类,获取callBack()函数的引用,并调用: <Callback handler> static void callback_handler(char *s) { int status; JNIEnv *env; bool isAttached = false; status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4); if(status < 0) { LOGE("callback_handler: failed to get JNI environment, " "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL); if(status < 0) { LOGE("callback_handler: failed to attach " "current thread"); return; } isAttached = true; } /* Construct a Java string */ jstring js = env->NewStringUTF(s); jclass interfaceClass = env->GetObjectClass(gInterfaceObject); if(!interfaceClass) { LOGE("callback_handler: failed to get class reference"); if(isAttached) gJavaVM->DetachCurrentThread(); return; } /* Find the callBack method ID */ jmethodID method = env->GetStaticMethodID( interfaceClass, "callBack", "(Ljava/lang/String;)V"); if(!method) { LOGE("callback_handler: failed to get method ID"); if(isAttached) gJavaVM->DetachCurrentThread(); return; } env->CallStaticVoidMethod(interfaceClass, method, js); if(isAttached) gJavaVM->DetachCurrentThread(); } 说明: 1、JNI GetEnv()函数返回的JNI环境对每个线程来说是独特的,因此我们必须在每次进入函数时都要重新获取。然而JavaVM指针是属于每个进程的,因此我们可以将其缓存起来(在JNI_OnLoad()函数中),在多个线程间使用。 2、当我们关联本地线程时,其相关的Java环境是与类引导程序一起的,这就意味着即使我们要在函数中获取一个类的引用(通常使用JNI函数FindClass()),都将会引发一个exception。因此我们使用缓存的JNIExampleInterface对象去获取类的引用(有趣的是,我们不能缓存类引用本身,JVM认为这种引用在本地代码中是不可见的,因此任何试图使用它都会触发JVM产生exception)。 3、为了获取callBack()的函数ID,我们需要指定其名称和JNI签名式。这里的签名式指出该函数需要一个java.lang.String对象作为参数,返回值为空。关于函数签名式的更多信息请参阅JNI文档,你可以使用javap工具去查询非本地函数的签名式(本地函数的签名式信息已经包含在javah产生的头文件中了)。 为了测试从本地线程中调用函数,我们需要一个在另一个独立线程中运行的函数。它唯一的任务就是调用callback handler: <Thread start function>= void *native_thread_start(void *arg) { sleep(1); callback_handler((char *) "Called from native thread"); } 现在我们已经有了实现callVoid()函数的所有本地部分的代码: <callVoid function> /* * Class: org_wooyd_android_JNIExample_JNIExampleInterface * Method: callVoid * Signature: ()V */ JNIEXPORT void JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface callVoid (JNIEnv *env, jclass cls) { pthread_t native_thread; callback_handler((char *) "Called from Java thread"); if(pthread_create(&native_thread, NULL, native_thread_start, NULL)) { LOGE("callVoid: failed to create a native thread"); } } 实现其他本地函数 getNewData()函数描述了如何在本地库中建立一个新的Java对象,并返回给调用者。为了获取类并创建其实例,我们再次使用缓存的Data对象。 <getNewData implementation>= /* * Class: org_wooyd_android_JNIExample_JNIExampleInterface * Method: getNewData * Signature: (ILjava/lang/String;)Lorg/wooyd/android/JNIExample/Data; */ JNIEXPORT jobject JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface_getNewData (JNIEnv *env, jclass cls, jint i, jstring s) { jclass dataClass = env->GetObjectClass(gDataObject); if(!dataClass) { LOGE("getNewData: failed to get class reference"); return NULL; } jmethodID dataConstructor = env->GetMethodID( dataClass, "<init>", "(ILjava/lang/String;)V"); if(!dataConstructor) { LOGE("getNewData: failed to get method ID"); return NULL; } jobject dataObject = env->NewObject(dataClass, dataConstructor, i, s); if(!dataObject) { LOGE("getNewData: failed to create an object"); return NULL; } return dataObject; } getDataString()函数描述了如何在本地函数中获取对象属性值。 <getDataString implementation>= /* * Class: org_wooyd_android_JNIExample_JNIExampleInterface * Method: getDataString * Signature: (Lorg/wooyd/android/JNIExample/Data;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface_getDataString (JNIEnv *env, jclass cls, jobject dataObject) { jclass dataClass = env->GetObjectClass(gDataObject); if(!dataClass) { LOGE("getDataString: failed to get class reference"); return NULL; } jfieldID dataStringField = env->GetFieldID( dataClass, "s", "Ljava/lang/String;"); if(!dataStringField) { LOGE("getDataString: failed to get field ID"); return NULL; } jstring dataStringValue = (jstring) env->GetObjectField( dataObject, dataStringField); return dataStringValue; } JNI_OnLoad()函数的实现 为了让JNI可以和Android JVM一起工作,必须提供JNI_OnLoad()函数。在本地库被装入到JVM时会调用该函数。之前我们已经提及许多任务要在该函数中完成,如:缓存全局JavaVM指针和对象实例。另外,我们需要在Java中调用的任何本地函数都必须注册,否则Android JVM将无法解析它们。具体函数实现如下: <JNIOnLoad implementation>= jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv *env; gJavaVM = vm; LOGI("JNI_OnLoad called"); if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("Failed to get the environment using GetEnv()"); return -1; } <Class instance caching> <Native function registration> return JNI_VERSION_1_4; } 由于本地线程无法存取functional classloader,所以我们需要将类引用缓存起来。正如之前所述,我们不能缓存类引用本身,我们缓存这些类的实例,之后我们可以通过GetObjectClass()JNI函数来获取类引用。 我们要记住的一点就是我们必须使用NewGlobalRef()函数将这些对象保护起来,以免被GC回收,这样就可以在JVM的整个生命周期内的任何线程中使用。建立实例并将其保存到全局变量是函数initClassHelper()的工作: <initClassHelper implementation>= void initClassHelper(JNIEnv *env, const char *path, jobject *objptr) { jclass cls = env->FindClass(path); if(!cls) { LOGE("initClassHelper: failed to get %s class reference", path); return; } jmethodID constr = env->GetMethodID(cls, "<init>", "()V"); if(!constr) { LOGE("initClassHelper: failed to get %s constructor", path); return; } jobject obj = env->NewObject(cls, constr); if(!obj) { LOGE("initClassHelper: failed to create a %s object", path); return; } (*objptr) = env->NewGlobalRef(obj); } 定义了这个函数,缓存类实例就是小菜一碟了 <Class instance caching> initClassHelper(env, kInterfacePath, &gInterfaceObject); initClassHelper(env, kDataPath, &gDataObject); 为了注册本地函数,我们建立一个JNINativeMethod结构的数组,该结构包含函数名称、签名式(可以从javah产生的注释中拷贝)、实现函数的指针。然后将这个数组传递给Android的registerNativeMethods()函数: <Native function registration> JNINativeMethod methods[] = { { "callVoid", "()V", (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_callVoid }, { "getNewData", "(ILjava/lang/String;)Lorg/wooyd/android/JNIExample/Data;", (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_getNewData }, { "getDataString", "(Lorg/wooyd/android/JNIExample/Data;)Ljava/lang/String;", (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_getDataString } }; if(android::AndroidRuntime::registerNativeMethods( env, kInterfacePath, methods, NELEM(methods)) != JNI_OK) { LOGE("Failed to register native methods"); return -1; } 编译本地库 这里仅介绍使用Android.mk编译本地库的方法,你必须先现在Android的整个源码。 为你的本地库建立一个目录,如:/path/to/android/source/code/vendor/your/sample。将Native的源码文件放到该目录下,并建立Android.mk文件,内容如下: # This makefile supplies the rules for building a library of JNI code for # use by our example platform shared library. LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional # This is the target being built. LOCAL_MODULE:= libjniexample # All of the source files that we will compile. LOCAL_SRC_FILES:= \ JNIExample.cpp # All of the shared libraries we link against. LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libnativehelper \ libcutils \ libutils # No static libraries. LOCAL_STATIC_LIBRARIES := # Include C headers #LOCAL_C_INCLUDES+= \ # $(call include-path-for, dbus) #LOCAL_C_INCLUDES +=\ # external/freetype/include \ # Also need the JNI headers. LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) # No specia compiler flags. LOCAL_CFLAGS += # Don't prelink this library. For more efficient code, you may want # to add this library to the prelink map and set this to true. LOCAL_PRELINK_MODULE := false include $(BUILD_SHARED_LIBRARY) 在Java代码中使用本地函数 我们将建立一个简单的activity,来使用JNI函数。我们唯一要做的就是在activity的onCreate()函数中load本地JNI库,使得其中定义的函数在Java中可用。整体结构如下: <JNIExample.java>= package org.wooyd.android.JNIExample; import android.app.Activity; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.os.Bundle; import android.os.Handler; import android.os.Message; import org.wooyd.android.JNIExample.JNIExampleInterface; import org.wooyd.android.JNIExample.Data; import android.util.Log; public class JNIExample extends Activity { TextView callVoidText, getNewDataText, getDataStringText; Button callVoidButton, getNewDataButton, getDataStringButton; Handler callbackHandler; JNIExampleInterface jniInterface; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); <Load JNI library> <callVoid demo> <getNewData demo> <getDataString demo> } } 首先使用System.loadLibrar安装库。 try { System.loadLibrary("libjniexample.so"; } catch (Exception ex) { Log.e("JNIExample", "failed to install native library: " + ex); } 接下来就是调用本地库函数并显示结果。为了演示callVoid(),我们必须先初始化一个handler,并将其传给JNI接口类,以使得我们能接受到callback消息: <callVoid demo> callVoidText = (TextView) findViewById(R.id.callVoid_text); callbackHandler = new Handler() { public void handleMessage(Message msg) { Bundle b = msg.getData(); callVoidText.setText(b.getString("callback_string")); } }; jniInterface = new JNIExampleInterface(callbackHandler); 建立一个按钮,按下的时候调用callVoid: <callVoid demo> callVoidButton = (Button) findViewById(R.id.callVoid_button); callVoidButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { jniInterface.callVoid(); } }); <getNewData demo> getNewDataText = (TextView) findViewById(R.id.getNewData_text); getNewDataButton = (Button) findViewById(R.id.getNewData_button); getNewDataButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { Data d = jniInterface.getNewData(42, "foo"); getNewDataText.setText( "getNewData(42, \"foo\") == Data(" + d.i + ", \"" + d.s + "\")"); } }); <getDataString demo> getDataStringText = (TextView) findViewById(R.id.getDataString_text); getDataStringButton = (Button) findViewById(R.id.getDataString_button); getDataStringButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { Data d = new Data(43, "bar"); String s = jniInterface.getDataString(d); getDataStringText.setText( "getDataString(Data(43, \"bar\")) == \"" + s + "\""); } }); references: JNIExample JNI references: Sun's Java Native Interface guide Java Native Interface: Programmer's Guide and Specification
最后我还在JNI_OnUnload里把那个全局变量的jobject给用DeleteGlobalRef掉。
相关文章推荐
- android jni c/c++线程通过CallVoidMethod调用java函数出现奔溃问题
- Android JNI开发,C调用Java方法遇到的问题
- 关于C++调用jar包,在创建虚拟机时使用JNI_CreateJavaVM调用失败返回-1的问题
- AndroidJNI 通过C++调用JAVA
- java 线程遇到的问题及解决方法 JNI调用
- 举例说明关于android编程中遇到的java.lang.ClassCastException: android.app.Application问题的原因及解决办法
- Android上的C/C++调用Java问题(转载)
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- 在Android中,通过JNI实现C++与Java相互调用
- jobject AllocObject(JNIEnv *env, jclass clazz); 分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用
- Android JNI通过C++调用JAVA
- Android JNI通过C++调用JAVA
- AndroidJNI 通过C++调用JAVA
- Java JNI调用c++ dll文件,传递参数乱码问题
- android jni (2) java与c++相互调用
- 举例说明Android开发中遇到的 java.lang.ClassCastException: java.lang.String这个问题的原因及其解决办法
- Android中简单的JNI使用,C++调用JAVA
- Android JNI 使用的数据结构JNINativeMethod详解 ||建立Android SDK下的JNI、JAVA应用完整步骤---Android JAVA调用C++代码
- Java JNI 调用C++ API及中文编码问题
- Java JNI 调用C++ API及中文编码问题