• 假设你想了解JNI在怎样在多线程下使用
  • 假设你在子线程使用JNI时遇到findClass不能找到目标Class,而在主线程下却能找到该Class的问题。或是GetEnv返回NULL的问题
  • 假设你想多学点编程技术的话


那么,这篇文章就是为你而写的, :)




近期工作中遇到这么个问题:c++代码须要调用Android的API来做一个比較耗时的任务,由于有点耗时,希望能有个进度条显示给用户,非常自然地,我创建了一个子线程用来执行这个耗时的任务,依照平时写法,结果一执行,GetEnv获取失败了。网上查找一番,官方说明有这么句话:


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​​.




调试后找到了原因,the current thread is not attached。 原来子线程函数里须要使用AttachCurrentThread()和DetachCurrentThread()这两个函数。没错,你须要gJvm->AttachCurrentThread(&env, NULL);来获取env,这样写之后,以为万事大吉了,结果findClass出错了,没有找到目标类,但是我千真万确地记得在主线程里这么写是没有问题的。env没有问题了,这回又哪里出错了呢?上网google一番,噢,不正确,google被墙了,是用bing查找一番后,总算有些眉目了。




首先确保你的class name写对了,以包名开头,并用反斜杠隔开。假设class name没有错,那么应该是class loader的问题了。解决方法是你先在主线程中获取该class,而且将其保存为全局变量,以便其它线程使用。


jclass tmp = env->FindClass("com/example/company/MyClass");  

myClass = (jclass)env->NewGlobalRef(tmp);




在子线程中,


mid = env->GetStaticMethodID(cls, "fromJNI", "(I)V");

if (mid != NULL)

{

    env->CallStaticVoidMethod(env, cls, mid, i);

}

当然,也有其它解决方法,至少我使用这样的方法成功了。而接下来在java中调用c++的代码就比較顺利了,木有碰到问题了。




总结:


1.在JNI_OnLoad中,保存JavaVM*,这是跨线程的,持久有效的,而JNIEnv*则是当前线程有效的。一旦启动线程,用AttachCurrentThread方法获得env。


2.通过JavaVM*和JNIEnv能够查找到jclass。


3.把jclass转成全局引用,使其跨线程。


4.然后就能够正常地调用你想调用的方法了。


5.用完后,别忘了delete掉创建的全局引用和调用DetachCurrentThread方法。