最近发行从c++中调用Java的方法会出现调用不到的现象,
例如:
我想在c++中调用如下java的方法
public class Test {
static int num = 0;
public static int testPthread(String str){
num++;
Log.e("Test","testPthread:"+str);
return num;
}
}
我开始的时候是这样实现的
pthread_t thread;
JNIEnv *get_env(int *attach) {
if (global_jvm == NULL) return NULL;
*attach = 0;
JNIEnv *jni_env = NULL;
int status = global_jvm->GetEnv((void **)&jni_env, JNI_VERSION_1_6);
LOGD("status:%d jni_env:%d",status,jni_env);
if (status == JNI_EDETACHED || jni_env == NULL) {
LOGD("AttachCurrentThread start");
status = global_jvm->AttachCurrentThread(&jni_env, NULL);
LOGD("AttachCurrentThread status:%d",status);
if (status < 0) {
jni_env = NULL;
} else {
*attach = 1;
}
}
return jni_env;
}
void del_env() {
global_jvm->DetachCurrentThread();
}
//void get_jvm(JNIEnv *env) {
// env->GetJavaVM(&global_jvm);
//}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_pthreaddemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
void * run(void * ch){
// //调用上层的方法
// JNIEnv *env;
// //从全局的JavaVM中获取到环境变量
// global_jvm->AttachCurrentThread(&env,NULL);
int attach = 0;
JNIEnv *env = get_env(&attach);
jstring jstr_data = env->NewStringUTF((char *)ch);
jclass clazz = env->FindClass("com/example/pthreaddemo/Test");
jmethodID methodID = env->GetStaticMethodID(clazz, "testPthread",
"(Ljava/lang/String;)I");
int result = env->CallStaticIntMethod(myClass, methodID, jstr_data);
env->DeleteLocalRef(jstr_data);
LOGD("pthreadTest Result:%d",result);
if (attach == 1) {
del_env();
}
return 0;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_pthreaddemo_MainActivity_pthreadTest(JNIEnv *env, jobject thiz, jstring src) {
// TODO: implement pthreadTest()
// pthread_t pthread;
char * ch = (char *)env->GetStringUTFChars(src,NULL);
pthread_create(&thread, NULL, run, (void *)ch);
pthread_detach(thread);
return 0;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
JNIEnv* env = NULL;
jint result = JNI_ERR;
global_jvm = vm;
/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
* GetEnv()函数返回的 Jni 环境对每个线程来说是不同的,
* 由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,
* 所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取
*
*/
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
return JNI_VERSION_1_4;
}
在c++中我使用pthread_create创建了一个线程,然后在线程中使用反射调用Java层的testPthread方法,果不其然然报错了
当调用FIndClass去寻找java层的类的时候,竟然报错找不到对应的类
于是网上百度了一下,发现,如果c++当前线程不是java层创建的线程,那么就会出现FindClass失效的情况,只能在Java创建的线程中去findClass才不会出错,于是将代码改为如下的获取方式
jclass myClass;
jmethodID methodID;
void * run(void * ch){
// //调用上层的方法
// JNIEnv *env;
// //从全局的JavaVM中获取到环境变量
// global_jvm->AttachCurrentThread(&env,NULL);
int attach = 0;
JNIEnv *env = get_env(&attach);
jstring jstr_data = env->NewStringUTF((char *)ch);
// jclass clazz = env->FindClass("com/example/pthreaddemo/Test");
// jmethodID methodID = env->GetStaticMethodID(clazz, "testPthread",
// "(Ljava/lang/String;)I");
int result = env->CallStaticIntMethod(myClass, methodID, jstr_data);
env->DeleteLocalRef(jstr_data);
LOGD("pthreadTest Result:%d",result);
if (attach == 1) {
del_env();
}
return 0;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_pthreaddemo_MainActivity_pthreadTest(JNIEnv *env, jobject thiz, jstring src) {
// TODO: implement pthreadTest()
// pthread_t pthread;
char * ch = (char *)env->GetStringUTFChars(src,NULL);
myClass = env->FindClass("com/example/pthreaddemo/Test");
methodID = env->GetStaticMethodID(myClass, "testPthread",
"(Ljava/lang/String;)I");
pthread_create(&thread, NULL, run, (void *)ch);
pthread_detach(thread);
return 0;
}
可以看到我是在创建线程前获取到的jclass的,并且放在了全局变量,也就是在java线程中获取的全局变量,但还是报了如下错误,大概意思是使用了一个被删除的引用
?于是又搜索了一番,发现findClass获取到的jcalss是局部引用,就算放到全局引用也是不起作用的,经过一番查询,终于找到如下解决方案,使用jni的NewGlobalRef 这个方法,将全局变量放到全局里,修改如下
?
jclass myClass;
jmethodID methodID;
void * run(void * ch){
// //调用上层的方法
// JNIEnv *env;
// //从全局的JavaVM中获取到环境变量
// global_jvm->AttachCurrentThread(&env,NULL);
int attach = 0;
JNIEnv *env = get_env(&attach);
jstring jstr_data = env->NewStringUTF((char *)ch);
int result = env->CallStaticIntMethod(myClass, methodID, jstr_data);
env->DeleteLocalRef(jstr_data);
env->DeleteGlobalRef(myClass);
LOGD("pthreadTest Result:%d",result);
if (attach == 1) {
del_env();
}
return 0;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_pthreaddemo_MainActivity_pthreadTest(JNIEnv *env, jobject thiz, jstring src) {
// TODO: implement pthreadTest()
// pthread_t pthread;
char * ch = (char *)env->GetStringUTFChars(src,NULL);
jclass tmp = env->FindClass("com/example/pthreaddemo/Test");
methodID = env->GetStaticMethodID(tmp, "testPthread",
"(Ljava/lang/String;)I");
myClass = (jclass)env->NewGlobalRef(tmp);
pthread_create(&thread, NULL, run, (void *)ch);
pthread_detach(thread);
return 0;
}
问题完美解决,最后总结一下:
1,jni中很多方法生成的对象都是局部引用,如果要放到全局,必须使用NewGlobalRef
2,如果当前线程是c++创建的线程,那么FindClass会有很大的概率找不到对应的java中的类,此时需要在java创建的线程中使用FindClass并放到全局中
|